sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 135 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 136 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 137 exp.DynamicProperty: lambda *_: "DYNAMIC", 138 exp.EmptyProperty: lambda *_: "EMPTY", 139 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 140 exp.EphemeralColumnConstraint: lambda self, 141 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 142 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 143 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 144 exp.Except: lambda self, e: self.set_operations(e), 145 exp.ExternalProperty: lambda *_: "EXTERNAL", 146 exp.Floor: lambda self, e: self.ceil_floor(e), 147 exp.GlobalProperty: lambda *_: "GLOBAL", 148 exp.HeapProperty: lambda *_: "HEAP", 149 exp.IcebergProperty: lambda *_: "ICEBERG", 150 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 151 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 152 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 153 exp.Intersect: lambda self, e: self.set_operations(e), 154 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 155 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 156 exp.LanguageProperty: lambda self, e: self.naked_property(e), 157 exp.LocationProperty: lambda self, e: self.naked_property(e), 158 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 159 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 160 exp.NonClusteredColumnConstraint: lambda self, 161 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 162 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 163 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 164 exp.OnCommitProperty: lambda _, 165 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 166 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 167 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 168 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 169 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 170 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 171 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 172 exp.ProjectionPolicyColumnConstraint: lambda self, 173 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 174 exp.RemoteWithConnectionModelProperty: lambda self, 175 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 176 exp.ReturnsProperty: lambda self, e: ( 177 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 178 ), 179 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 180 exp.SecureProperty: lambda *_: "SECURE", 181 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 182 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 183 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 184 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 185 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 186 exp.SqlReadWriteProperty: lambda _, e: e.name, 187 exp.SqlSecurityProperty: lambda _, 188 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 189 exp.StabilityProperty: lambda _, e: e.name, 190 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 191 exp.StreamingTableProperty: lambda *_: "STREAMING", 192 exp.StrictProperty: lambda *_: "STRICT", 193 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 194 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 195 exp.TemporaryProperty: lambda *_: "TEMPORARY", 196 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 197 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 198 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 199 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 200 exp.TransientProperty: lambda *_: "TRANSIENT", 201 exp.Union: lambda self, e: self.set_operations(e), 202 exp.UnloggedProperty: lambda *_: "UNLOGGED", 203 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 204 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 205 exp.Uuid: lambda *_: "UUID()", 206 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 207 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 208 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 209 exp.VolatileProperty: lambda *_: "VOLATILE", 210 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 211 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 212 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 213 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 214 exp.ForceProperty: lambda *_: "FORCE", 215 } 216 217 # Whether null ordering is supported in order by 218 # True: Full Support, None: No support, False: No support for certain cases 219 # such as window specifications, aggregate functions etc 220 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 221 222 # Whether ignore nulls is inside the agg or outside. 223 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 224 IGNORE_NULLS_IN_FUNC = False 225 226 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 227 LOCKING_READS_SUPPORTED = False 228 229 # Whether the EXCEPT and INTERSECT operations can return duplicates 230 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 231 232 # Wrap derived values in parens, usually standard but spark doesn't support it 233 WRAP_DERIVED_VALUES = True 234 235 # Whether create function uses an AS before the RETURN 236 CREATE_FUNCTION_RETURN_AS = True 237 238 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 239 MATCHED_BY_SOURCE = True 240 241 # Whether the INTERVAL expression works only with values like '1 day' 242 SINGLE_STRING_INTERVAL = False 243 244 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 245 INTERVAL_ALLOWS_PLURAL_FORM = True 246 247 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 248 LIMIT_FETCH = "ALL" 249 250 # Whether limit and fetch allows expresions or just limits 251 LIMIT_ONLY_LITERALS = False 252 253 # Whether a table is allowed to be renamed with a db 254 RENAME_TABLE_WITH_DB = True 255 256 # The separator for grouping sets and rollups 257 GROUPINGS_SEP = "," 258 259 # The string used for creating an index on a table 260 INDEX_ON = "ON" 261 262 # Whether join hints should be generated 263 JOIN_HINTS = True 264 265 # Whether table hints should be generated 266 TABLE_HINTS = True 267 268 # Whether query hints should be generated 269 QUERY_HINTS = True 270 271 # What kind of separator to use for query hints 272 QUERY_HINT_SEP = ", " 273 274 # Whether comparing against booleans (e.g. x IS TRUE) is supported 275 IS_BOOL_ALLOWED = True 276 277 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 278 DUPLICATE_KEY_UPDATE_WITH_SET = True 279 280 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 281 LIMIT_IS_TOP = False 282 283 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 284 RETURNING_END = True 285 286 # Whether to generate an unquoted value for EXTRACT's date part argument 287 EXTRACT_ALLOWS_QUOTES = True 288 289 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 290 TZ_TO_WITH_TIME_ZONE = False 291 292 # Whether the NVL2 function is supported 293 NVL2_SUPPORTED = True 294 295 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 296 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 297 298 # Whether VALUES statements can be used as derived tables. 299 # MySQL 5 and Redshift do not allow this, so when False, it will convert 300 # SELECT * VALUES into SELECT UNION 301 VALUES_AS_TABLE = True 302 303 # Whether the word COLUMN is included when adding a column with ALTER TABLE 304 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 305 306 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 307 UNNEST_WITH_ORDINALITY = True 308 309 # Whether FILTER (WHERE cond) can be used for conditional aggregation 310 AGGREGATE_FILTER_SUPPORTED = True 311 312 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 313 SEMI_ANTI_JOIN_WITH_SIDE = True 314 315 # Whether to include the type of a computed column in the CREATE DDL 316 COMPUTED_COLUMN_WITH_TYPE = True 317 318 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 319 SUPPORTS_TABLE_COPY = True 320 321 # Whether parentheses are required around the table sample's expression 322 TABLESAMPLE_REQUIRES_PARENS = True 323 324 # Whether a table sample clause's size needs to be followed by the ROWS keyword 325 TABLESAMPLE_SIZE_IS_ROWS = True 326 327 # The keyword(s) to use when generating a sample clause 328 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 329 330 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 331 TABLESAMPLE_WITH_METHOD = True 332 333 # The keyword to use when specifying the seed of a sample clause 334 TABLESAMPLE_SEED_KEYWORD = "SEED" 335 336 # Whether COLLATE is a function instead of a binary operator 337 COLLATE_IS_FUNC = False 338 339 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 340 DATA_TYPE_SPECIFIERS_ALLOWED = False 341 342 # Whether conditions require booleans WHERE x = 0 vs WHERE x 343 ENSURE_BOOLS = False 344 345 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 346 CTE_RECURSIVE_KEYWORD_REQUIRED = True 347 348 # Whether CONCAT requires >1 arguments 349 SUPPORTS_SINGLE_ARG_CONCAT = True 350 351 # Whether LAST_DAY function supports a date part argument 352 LAST_DAY_SUPPORTS_DATE_PART = True 353 354 # Whether named columns are allowed in table aliases 355 SUPPORTS_TABLE_ALIAS_COLUMNS = True 356 357 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 358 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 359 360 # What delimiter to use for separating JSON key/value pairs 361 JSON_KEY_VALUE_PAIR_SEP = ":" 362 363 # INSERT OVERWRITE TABLE x override 364 INSERT_OVERWRITE = " OVERWRITE TABLE" 365 366 # Whether the SELECT .. INTO syntax is used instead of CTAS 367 SUPPORTS_SELECT_INTO = False 368 369 # Whether UNLOGGED tables can be created 370 SUPPORTS_UNLOGGED_TABLES = False 371 372 # Whether the CREATE TABLE LIKE statement is supported 373 SUPPORTS_CREATE_TABLE_LIKE = True 374 375 # Whether the LikeProperty needs to be specified inside of the schema clause 376 LIKE_PROPERTY_INSIDE_SCHEMA = False 377 378 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 379 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 380 MULTI_ARG_DISTINCT = True 381 382 # Whether the JSON extraction operators expect a value of type JSON 383 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 384 385 # Whether bracketed keys like ["foo"] are supported in JSON paths 386 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 387 388 # Whether to escape keys using single quotes in JSON paths 389 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 390 391 # The JSONPathPart expressions supported by this dialect 392 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 393 394 # Whether any(f(x) for x in array) can be implemented by this dialect 395 CAN_IMPLEMENT_ARRAY_ANY = False 396 397 # Whether the function TO_NUMBER is supported 398 SUPPORTS_TO_NUMBER = True 399 400 # Whether or not set op modifiers apply to the outer set op or select. 401 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 402 # True means limit 1 happens after the set op, False means it it happens on y. 403 SET_OP_MODIFIERS = True 404 405 # Whether parameters from COPY statement are wrapped in parentheses 406 COPY_PARAMS_ARE_WRAPPED = True 407 408 # Whether values of params are set with "=" token or empty space 409 COPY_PARAMS_EQ_REQUIRED = False 410 411 # Whether COPY statement has INTO keyword 412 COPY_HAS_INTO_KEYWORD = True 413 414 # Whether the conditional TRY(expression) function is supported 415 TRY_SUPPORTED = True 416 417 # Whether the UESCAPE syntax in unicode strings is supported 418 SUPPORTS_UESCAPE = True 419 420 # The keyword to use when generating a star projection with excluded columns 421 STAR_EXCEPT = "EXCEPT" 422 423 # The HEX function name 424 HEX_FUNC = "HEX" 425 426 # The keywords to use when prefixing & separating WITH based properties 427 WITH_PROPERTIES_PREFIX = "WITH" 428 429 # Whether to quote the generated expression of exp.JsonPath 430 QUOTE_JSON_PATH = True 431 432 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 433 PAD_FILL_PATTERN_IS_REQUIRED = False 434 435 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 436 SUPPORTS_EXPLODING_PROJECTIONS = True 437 438 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 439 ARRAY_CONCAT_IS_VAR_LEN = True 440 441 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 442 SUPPORTS_CONVERT_TIMEZONE = False 443 444 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 445 SUPPORTS_MEDIAN = True 446 447 # Whether UNIX_SECONDS(timestamp) is supported 448 SUPPORTS_UNIX_SECONDS = False 449 450 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 451 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 452 453 # The function name of the exp.ArraySize expression 454 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 455 456 # The syntax to use when altering the type of a column 457 ALTER_SET_TYPE = "SET DATA TYPE" 458 459 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 460 # None -> Doesn't support it at all 461 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 462 # True (Postgres) -> Explicitly requires it 463 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 464 465 TYPE_MAPPING = { 466 exp.DataType.Type.DATETIME2: "TIMESTAMP", 467 exp.DataType.Type.NCHAR: "CHAR", 468 exp.DataType.Type.NVARCHAR: "VARCHAR", 469 exp.DataType.Type.MEDIUMTEXT: "TEXT", 470 exp.DataType.Type.LONGTEXT: "TEXT", 471 exp.DataType.Type.TINYTEXT: "TEXT", 472 exp.DataType.Type.BLOB: "VARBINARY", 473 exp.DataType.Type.MEDIUMBLOB: "BLOB", 474 exp.DataType.Type.LONGBLOB: "BLOB", 475 exp.DataType.Type.TINYBLOB: "BLOB", 476 exp.DataType.Type.INET: "INET", 477 exp.DataType.Type.ROWVERSION: "VARBINARY", 478 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 479 } 480 481 TIME_PART_SINGULARS = { 482 "MICROSECONDS": "MICROSECOND", 483 "SECONDS": "SECOND", 484 "MINUTES": "MINUTE", 485 "HOURS": "HOUR", 486 "DAYS": "DAY", 487 "WEEKS": "WEEK", 488 "MONTHS": "MONTH", 489 "QUARTERS": "QUARTER", 490 "YEARS": "YEAR", 491 } 492 493 AFTER_HAVING_MODIFIER_TRANSFORMS = { 494 "cluster": lambda self, e: self.sql(e, "cluster"), 495 "distribute": lambda self, e: self.sql(e, "distribute"), 496 "sort": lambda self, e: self.sql(e, "sort"), 497 "windows": lambda self, e: ( 498 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 499 if e.args.get("windows") 500 else "" 501 ), 502 "qualify": lambda self, e: self.sql(e, "qualify"), 503 } 504 505 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 506 507 STRUCT_DELIMITER = ("<", ">") 508 509 PARAMETER_TOKEN = "@" 510 NAMED_PLACEHOLDER_TOKEN = ":" 511 512 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 513 514 PROPERTIES_LOCATION = { 515 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 516 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 517 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 518 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 519 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 521 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 522 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 523 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 524 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 525 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 526 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 527 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 528 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 529 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 530 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 531 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 532 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 533 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 534 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 535 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 536 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 539 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 540 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 542 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 543 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 544 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 545 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 546 exp.HeapProperty: exp.Properties.Location.POST_WITH, 547 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 549 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 552 exp.JournalProperty: exp.Properties.Location.POST_NAME, 553 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 558 exp.LogProperty: exp.Properties.Location.POST_NAME, 559 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 560 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 561 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 562 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 564 exp.Order: exp.Properties.Location.POST_SCHEMA, 565 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 567 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 569 exp.Property: exp.Properties.Location.POST_WITH, 570 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 578 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 580 exp.Set: exp.Properties.Location.POST_SCHEMA, 581 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.SetProperty: exp.Properties.Location.POST_CREATE, 583 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 585 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 586 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 589 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 592 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.Tags: exp.Properties.Location.POST_WITH, 594 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 595 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 597 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 599 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 600 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 603 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 604 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 605 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 606 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 609 } 610 611 # Keywords that can't be used as unquoted identifier names 612 RESERVED_KEYWORDS: t.Set[str] = set() 613 614 # Expressions whose comments are separated from them for better formatting 615 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 616 exp.Command, 617 exp.Create, 618 exp.Describe, 619 exp.Delete, 620 exp.Drop, 621 exp.From, 622 exp.Insert, 623 exp.Join, 624 exp.MultitableInserts, 625 exp.Select, 626 exp.SetOperation, 627 exp.Update, 628 exp.Where, 629 exp.With, 630 ) 631 632 # Expressions that should not have their comments generated in maybe_comment 633 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 634 exp.Binary, 635 exp.SetOperation, 636 ) 637 638 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 639 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 640 exp.Column, 641 exp.Literal, 642 exp.Neg, 643 exp.Paren, 644 ) 645 646 PARAMETERIZABLE_TEXT_TYPES = { 647 exp.DataType.Type.NVARCHAR, 648 exp.DataType.Type.VARCHAR, 649 exp.DataType.Type.CHAR, 650 exp.DataType.Type.NCHAR, 651 } 652 653 # Expressions that need to have all CTEs under them bubbled up to them 654 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 655 656 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 657 658 __slots__ = ( 659 "pretty", 660 "identify", 661 "normalize", 662 "pad", 663 "_indent", 664 "normalize_functions", 665 "unsupported_level", 666 "max_unsupported", 667 "leading_comma", 668 "max_text_width", 669 "comments", 670 "dialect", 671 "unsupported_messages", 672 "_escaped_quote_end", 673 "_escaped_identifier_end", 674 "_next_name", 675 "_identifier_start", 676 "_identifier_end", 677 "_quote_json_path_key_using_brackets", 678 ) 679 680 def __init__( 681 self, 682 pretty: t.Optional[bool] = None, 683 identify: str | bool = False, 684 normalize: bool = False, 685 pad: int = 2, 686 indent: int = 2, 687 normalize_functions: t.Optional[str | bool] = None, 688 unsupported_level: ErrorLevel = ErrorLevel.WARN, 689 max_unsupported: int = 3, 690 leading_comma: bool = False, 691 max_text_width: int = 80, 692 comments: bool = True, 693 dialect: DialectType = None, 694 ): 695 import sqlglot 696 from sqlglot.dialects import Dialect 697 698 self.pretty = pretty if pretty is not None else sqlglot.pretty 699 self.identify = identify 700 self.normalize = normalize 701 self.pad = pad 702 self._indent = indent 703 self.unsupported_level = unsupported_level 704 self.max_unsupported = max_unsupported 705 self.leading_comma = leading_comma 706 self.max_text_width = max_text_width 707 self.comments = comments 708 self.dialect = Dialect.get_or_raise(dialect) 709 710 # This is both a Dialect property and a Generator argument, so we prioritize the latter 711 self.normalize_functions = ( 712 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 713 ) 714 715 self.unsupported_messages: t.List[str] = [] 716 self._escaped_quote_end: str = ( 717 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 718 ) 719 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 720 721 self._next_name = name_sequence("_t") 722 723 self._identifier_start = self.dialect.IDENTIFIER_START 724 self._identifier_end = self.dialect.IDENTIFIER_END 725 726 self._quote_json_path_key_using_brackets = True 727 728 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 729 """ 730 Generates the SQL string corresponding to the given syntax tree. 731 732 Args: 733 expression: The syntax tree. 734 copy: Whether to copy the expression. The generator performs mutations so 735 it is safer to copy. 736 737 Returns: 738 The SQL string corresponding to `expression`. 739 """ 740 if copy: 741 expression = expression.copy() 742 743 expression = self.preprocess(expression) 744 745 self.unsupported_messages = [] 746 sql = self.sql(expression).strip() 747 748 if self.pretty: 749 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 750 751 if self.unsupported_level == ErrorLevel.IGNORE: 752 return sql 753 754 if self.unsupported_level == ErrorLevel.WARN: 755 for msg in self.unsupported_messages: 756 logger.warning(msg) 757 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 758 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 759 760 return sql 761 762 def preprocess(self, expression: exp.Expression) -> exp.Expression: 763 """Apply generic preprocessing transformations to a given expression.""" 764 expression = self._move_ctes_to_top_level(expression) 765 766 if self.ENSURE_BOOLS: 767 from sqlglot.transforms import ensure_bools 768 769 expression = ensure_bools(expression) 770 771 return expression 772 773 def _move_ctes_to_top_level(self, expression: E) -> E: 774 if ( 775 not expression.parent 776 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 777 and any(node.parent is not expression for node in expression.find_all(exp.With)) 778 ): 779 from sqlglot.transforms import move_ctes_to_top_level 780 781 expression = move_ctes_to_top_level(expression) 782 return expression 783 784 def unsupported(self, message: str) -> None: 785 if self.unsupported_level == ErrorLevel.IMMEDIATE: 786 raise UnsupportedError(message) 787 self.unsupported_messages.append(message) 788 789 def sep(self, sep: str = " ") -> str: 790 return f"{sep.strip()}\n" if self.pretty else sep 791 792 def seg(self, sql: str, sep: str = " ") -> str: 793 return f"{self.sep(sep)}{sql}" 794 795 def pad_comment(self, comment: str) -> str: 796 comment = " " + comment if comment[0].strip() else comment 797 comment = comment + " " if comment[-1].strip() else comment 798 return comment 799 800 def maybe_comment( 801 self, 802 sql: str, 803 expression: t.Optional[exp.Expression] = None, 804 comments: t.Optional[t.List[str]] = None, 805 separated: bool = False, 806 ) -> str: 807 comments = ( 808 ((expression and expression.comments) if comments is None else comments) # type: ignore 809 if self.comments 810 else None 811 ) 812 813 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 814 return sql 815 816 comments_sql = " ".join( 817 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 818 ) 819 820 if not comments_sql: 821 return sql 822 823 comments_sql = self._replace_line_breaks(comments_sql) 824 825 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 826 return ( 827 f"{self.sep()}{comments_sql}{sql}" 828 if not sql or sql[0].isspace() 829 else f"{comments_sql}{self.sep()}{sql}" 830 ) 831 832 return f"{sql} {comments_sql}" 833 834 def wrap(self, expression: exp.Expression | str) -> str: 835 this_sql = ( 836 self.sql(expression) 837 if isinstance(expression, exp.UNWRAPPED_QUERIES) 838 else self.sql(expression, "this") 839 ) 840 if not this_sql: 841 return "()" 842 843 this_sql = self.indent(this_sql, level=1, pad=0) 844 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 845 846 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 847 original = self.identify 848 self.identify = False 849 result = func(*args, **kwargs) 850 self.identify = original 851 return result 852 853 def normalize_func(self, name: str) -> str: 854 if self.normalize_functions == "upper" or self.normalize_functions is True: 855 return name.upper() 856 if self.normalize_functions == "lower": 857 return name.lower() 858 return name 859 860 def indent( 861 self, 862 sql: str, 863 level: int = 0, 864 pad: t.Optional[int] = None, 865 skip_first: bool = False, 866 skip_last: bool = False, 867 ) -> str: 868 if not self.pretty or not sql: 869 return sql 870 871 pad = self.pad if pad is None else pad 872 lines = sql.split("\n") 873 874 return "\n".join( 875 ( 876 line 877 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 878 else f"{' ' * (level * self._indent + pad)}{line}" 879 ) 880 for i, line in enumerate(lines) 881 ) 882 883 def sql( 884 self, 885 expression: t.Optional[str | exp.Expression], 886 key: t.Optional[str] = None, 887 comment: bool = True, 888 ) -> str: 889 if not expression: 890 return "" 891 892 if isinstance(expression, str): 893 return expression 894 895 if key: 896 value = expression.args.get(key) 897 if value: 898 return self.sql(value) 899 return "" 900 901 transform = self.TRANSFORMS.get(expression.__class__) 902 903 if callable(transform): 904 sql = transform(self, expression) 905 elif isinstance(expression, exp.Expression): 906 exp_handler_name = f"{expression.key}_sql" 907 908 if hasattr(self, exp_handler_name): 909 sql = getattr(self, exp_handler_name)(expression) 910 elif isinstance(expression, exp.Func): 911 sql = self.function_fallback_sql(expression) 912 elif isinstance(expression, exp.Property): 913 sql = self.property_sql(expression) 914 else: 915 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 916 else: 917 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 918 919 return self.maybe_comment(sql, expression) if self.comments and comment else sql 920 921 def uncache_sql(self, expression: exp.Uncache) -> str: 922 table = self.sql(expression, "this") 923 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 924 return f"UNCACHE TABLE{exists_sql} {table}" 925 926 def cache_sql(self, expression: exp.Cache) -> str: 927 lazy = " LAZY" if expression.args.get("lazy") else "" 928 table = self.sql(expression, "this") 929 options = expression.args.get("options") 930 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 931 sql = self.sql(expression, "expression") 932 sql = f" AS{self.sep()}{sql}" if sql else "" 933 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 934 return self.prepend_ctes(expression, sql) 935 936 def characterset_sql(self, expression: exp.CharacterSet) -> str: 937 if isinstance(expression.parent, exp.Cast): 938 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 939 default = "DEFAULT " if expression.args.get("default") else "" 940 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 941 942 def column_parts(self, expression: exp.Column) -> str: 943 return ".".join( 944 self.sql(part) 945 for part in ( 946 expression.args.get("catalog"), 947 expression.args.get("db"), 948 expression.args.get("table"), 949 expression.args.get("this"), 950 ) 951 if part 952 ) 953 954 def column_sql(self, expression: exp.Column) -> str: 955 join_mark = " (+)" if expression.args.get("join_mark") else "" 956 957 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 958 join_mark = "" 959 self.unsupported("Outer join syntax using the (+) operator is not supported.") 960 961 return f"{self.column_parts(expression)}{join_mark}" 962 963 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 964 this = self.sql(expression, "this") 965 this = f" {this}" if this else "" 966 position = self.sql(expression, "position") 967 return f"{position}{this}" 968 969 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 970 column = self.sql(expression, "this") 971 kind = self.sql(expression, "kind") 972 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 973 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 974 kind = f"{sep}{kind}" if kind else "" 975 constraints = f" {constraints}" if constraints else "" 976 position = self.sql(expression, "position") 977 position = f" {position}" if position else "" 978 979 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 980 kind = "" 981 982 return f"{exists}{column}{kind}{constraints}{position}" 983 984 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 985 this = self.sql(expression, "this") 986 kind_sql = self.sql(expression, "kind").strip() 987 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 988 989 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 990 this = self.sql(expression, "this") 991 if expression.args.get("not_null"): 992 persisted = " PERSISTED NOT NULL" 993 elif expression.args.get("persisted"): 994 persisted = " PERSISTED" 995 else: 996 persisted = "" 997 return f"AS {this}{persisted}" 998 999 def autoincrementcolumnconstraint_sql(self, _) -> str: 1000 return self.token_sql(TokenType.AUTO_INCREMENT) 1001 1002 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1003 if isinstance(expression.this, list): 1004 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1005 else: 1006 this = self.sql(expression, "this") 1007 1008 return f"COMPRESS {this}" 1009 1010 def generatedasidentitycolumnconstraint_sql( 1011 self, expression: exp.GeneratedAsIdentityColumnConstraint 1012 ) -> str: 1013 this = "" 1014 if expression.this is not None: 1015 on_null = " ON NULL" if expression.args.get("on_null") else "" 1016 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1017 1018 start = expression.args.get("start") 1019 start = f"START WITH {start}" if start else "" 1020 increment = expression.args.get("increment") 1021 increment = f" INCREMENT BY {increment}" if increment else "" 1022 minvalue = expression.args.get("minvalue") 1023 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1024 maxvalue = expression.args.get("maxvalue") 1025 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1026 cycle = expression.args.get("cycle") 1027 cycle_sql = "" 1028 1029 if cycle is not None: 1030 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1031 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1032 1033 sequence_opts = "" 1034 if start or increment or cycle_sql: 1035 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1036 sequence_opts = f" ({sequence_opts.strip()})" 1037 1038 expr = self.sql(expression, "expression") 1039 expr = f"({expr})" if expr else "IDENTITY" 1040 1041 return f"GENERATED{this} AS {expr}{sequence_opts}" 1042 1043 def generatedasrowcolumnconstraint_sql( 1044 self, expression: exp.GeneratedAsRowColumnConstraint 1045 ) -> str: 1046 start = "START" if expression.args.get("start") else "END" 1047 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1048 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1049 1050 def periodforsystemtimeconstraint_sql( 1051 self, expression: exp.PeriodForSystemTimeConstraint 1052 ) -> str: 1053 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1054 1055 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1056 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1057 1058 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1059 return f"AS {self.sql(expression, 'this')}" 1060 1061 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1062 desc = expression.args.get("desc") 1063 if desc is not None: 1064 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1065 return "PRIMARY KEY" 1066 1067 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1068 this = self.sql(expression, "this") 1069 this = f" {this}" if this else "" 1070 index_type = expression.args.get("index_type") 1071 index_type = f" USING {index_type}" if index_type else "" 1072 on_conflict = self.sql(expression, "on_conflict") 1073 on_conflict = f" {on_conflict}" if on_conflict else "" 1074 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1075 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1076 1077 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1078 return self.sql(expression, "this") 1079 1080 def create_sql(self, expression: exp.Create) -> str: 1081 kind = self.sql(expression, "kind") 1082 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1083 properties = expression.args.get("properties") 1084 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1085 1086 this = self.createable_sql(expression, properties_locs) 1087 1088 properties_sql = "" 1089 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1090 exp.Properties.Location.POST_WITH 1091 ): 1092 properties_sql = self.sql( 1093 exp.Properties( 1094 expressions=[ 1095 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1096 *properties_locs[exp.Properties.Location.POST_WITH], 1097 ] 1098 ) 1099 ) 1100 1101 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1102 properties_sql = self.sep() + properties_sql 1103 elif not self.pretty: 1104 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1105 properties_sql = f" {properties_sql}" 1106 1107 begin = " BEGIN" if expression.args.get("begin") else "" 1108 end = " END" if expression.args.get("end") else "" 1109 1110 expression_sql = self.sql(expression, "expression") 1111 if expression_sql: 1112 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1113 1114 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1115 postalias_props_sql = "" 1116 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1117 postalias_props_sql = self.properties( 1118 exp.Properties( 1119 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1120 ), 1121 wrapped=False, 1122 ) 1123 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1124 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1125 1126 postindex_props_sql = "" 1127 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1128 postindex_props_sql = self.properties( 1129 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1130 wrapped=False, 1131 prefix=" ", 1132 ) 1133 1134 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1135 indexes = f" {indexes}" if indexes else "" 1136 index_sql = indexes + postindex_props_sql 1137 1138 replace = " OR REPLACE" if expression.args.get("replace") else "" 1139 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1140 unique = " UNIQUE" if expression.args.get("unique") else "" 1141 1142 clustered = expression.args.get("clustered") 1143 if clustered is None: 1144 clustered_sql = "" 1145 elif clustered: 1146 clustered_sql = " CLUSTERED COLUMNSTORE" 1147 else: 1148 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1149 1150 postcreate_props_sql = "" 1151 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1152 postcreate_props_sql = self.properties( 1153 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1154 sep=" ", 1155 prefix=" ", 1156 wrapped=False, 1157 ) 1158 1159 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1160 1161 postexpression_props_sql = "" 1162 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1163 postexpression_props_sql = self.properties( 1164 exp.Properties( 1165 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1166 ), 1167 sep=" ", 1168 prefix=" ", 1169 wrapped=False, 1170 ) 1171 1172 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1173 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1174 no_schema_binding = ( 1175 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1176 ) 1177 1178 clone = self.sql(expression, "clone") 1179 clone = f" {clone}" if clone else "" 1180 1181 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1182 properties_expression = f"{expression_sql}{properties_sql}" 1183 else: 1184 properties_expression = f"{properties_sql}{expression_sql}" 1185 1186 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1187 return self.prepend_ctes(expression, expression_sql) 1188 1189 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1190 start = self.sql(expression, "start") 1191 start = f"START WITH {start}" if start else "" 1192 increment = self.sql(expression, "increment") 1193 increment = f" INCREMENT BY {increment}" if increment else "" 1194 minvalue = self.sql(expression, "minvalue") 1195 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1196 maxvalue = self.sql(expression, "maxvalue") 1197 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1198 owned = self.sql(expression, "owned") 1199 owned = f" OWNED BY {owned}" if owned else "" 1200 1201 cache = expression.args.get("cache") 1202 if cache is None: 1203 cache_str = "" 1204 elif cache is True: 1205 cache_str = " CACHE" 1206 else: 1207 cache_str = f" CACHE {cache}" 1208 1209 options = self.expressions(expression, key="options", flat=True, sep=" ") 1210 options = f" {options}" if options else "" 1211 1212 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1213 1214 def clone_sql(self, expression: exp.Clone) -> str: 1215 this = self.sql(expression, "this") 1216 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1217 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1218 return f"{shallow}{keyword} {this}" 1219 1220 def describe_sql(self, expression: exp.Describe) -> str: 1221 style = expression.args.get("style") 1222 style = f" {style}" if style else "" 1223 partition = self.sql(expression, "partition") 1224 partition = f" {partition}" if partition else "" 1225 format = self.sql(expression, "format") 1226 format = f" {format}" if format else "" 1227 1228 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1229 1230 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1231 tag = self.sql(expression, "tag") 1232 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1233 1234 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1235 with_ = self.sql(expression, "with") 1236 if with_: 1237 sql = f"{with_}{self.sep()}{sql}" 1238 return sql 1239 1240 def with_sql(self, expression: exp.With) -> str: 1241 sql = self.expressions(expression, flat=True) 1242 recursive = ( 1243 "RECURSIVE " 1244 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1245 else "" 1246 ) 1247 search = self.sql(expression, "search") 1248 search = f" {search}" if search else "" 1249 1250 return f"WITH {recursive}{sql}{search}" 1251 1252 def cte_sql(self, expression: exp.CTE) -> str: 1253 alias = expression.args.get("alias") 1254 if alias: 1255 alias.add_comments(expression.pop_comments()) 1256 1257 alias_sql = self.sql(expression, "alias") 1258 1259 materialized = expression.args.get("materialized") 1260 if materialized is False: 1261 materialized = "NOT MATERIALIZED " 1262 elif materialized: 1263 materialized = "MATERIALIZED " 1264 1265 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1266 1267 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1268 alias = self.sql(expression, "this") 1269 columns = self.expressions(expression, key="columns", flat=True) 1270 columns = f"({columns})" if columns else "" 1271 1272 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1273 columns = "" 1274 self.unsupported("Named columns are not supported in table alias.") 1275 1276 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1277 alias = self._next_name() 1278 1279 return f"{alias}{columns}" 1280 1281 def bitstring_sql(self, expression: exp.BitString) -> str: 1282 this = self.sql(expression, "this") 1283 if self.dialect.BIT_START: 1284 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1285 return f"{int(this, 2)}" 1286 1287 def hexstring_sql( 1288 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1289 ) -> str: 1290 this = self.sql(expression, "this") 1291 is_integer_type = expression.args.get("is_integer") 1292 1293 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1294 not self.dialect.HEX_START and not binary_function_repr 1295 ): 1296 # Integer representation will be returned if: 1297 # - The read dialect treats the hex value as integer literal but not the write 1298 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1299 return f"{int(this, 16)}" 1300 1301 if not is_integer_type: 1302 # Read dialect treats the hex value as BINARY/BLOB 1303 if binary_function_repr: 1304 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1305 return self.func(binary_function_repr, exp.Literal.string(this)) 1306 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1307 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1308 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1309 1310 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1311 1312 def bytestring_sql(self, expression: exp.ByteString) -> str: 1313 this = self.sql(expression, "this") 1314 if self.dialect.BYTE_START: 1315 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1316 return this 1317 1318 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1319 this = self.sql(expression, "this") 1320 escape = expression.args.get("escape") 1321 1322 if self.dialect.UNICODE_START: 1323 escape_substitute = r"\\\1" 1324 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1325 else: 1326 escape_substitute = r"\\u\1" 1327 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1328 1329 if escape: 1330 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1331 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1332 else: 1333 escape_pattern = ESCAPED_UNICODE_RE 1334 escape_sql = "" 1335 1336 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1337 this = escape_pattern.sub(escape_substitute, this) 1338 1339 return f"{left_quote}{this}{right_quote}{escape_sql}" 1340 1341 def rawstring_sql(self, expression: exp.RawString) -> str: 1342 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1343 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1344 1345 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1346 this = self.sql(expression, "this") 1347 specifier = self.sql(expression, "expression") 1348 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1349 return f"{this}{specifier}" 1350 1351 def datatype_sql(self, expression: exp.DataType) -> str: 1352 nested = "" 1353 values = "" 1354 interior = self.expressions(expression, flat=True) 1355 1356 type_value = expression.this 1357 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1358 type_sql = self.sql(expression, "kind") 1359 else: 1360 type_sql = ( 1361 self.TYPE_MAPPING.get(type_value, type_value.value) 1362 if isinstance(type_value, exp.DataType.Type) 1363 else type_value 1364 ) 1365 1366 if interior: 1367 if expression.args.get("nested"): 1368 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1369 if expression.args.get("values") is not None: 1370 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1371 values = self.expressions(expression, key="values", flat=True) 1372 values = f"{delimiters[0]}{values}{delimiters[1]}" 1373 elif type_value == exp.DataType.Type.INTERVAL: 1374 nested = f" {interior}" 1375 else: 1376 nested = f"({interior})" 1377 1378 type_sql = f"{type_sql}{nested}{values}" 1379 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1380 exp.DataType.Type.TIMETZ, 1381 exp.DataType.Type.TIMESTAMPTZ, 1382 ): 1383 type_sql = f"{type_sql} WITH TIME ZONE" 1384 1385 return type_sql 1386 1387 def directory_sql(self, expression: exp.Directory) -> str: 1388 local = "LOCAL " if expression.args.get("local") else "" 1389 row_format = self.sql(expression, "row_format") 1390 row_format = f" {row_format}" if row_format else "" 1391 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1392 1393 def delete_sql(self, expression: exp.Delete) -> str: 1394 this = self.sql(expression, "this") 1395 this = f" FROM {this}" if this else "" 1396 using = self.sql(expression, "using") 1397 using = f" USING {using}" if using else "" 1398 cluster = self.sql(expression, "cluster") 1399 cluster = f" {cluster}" if cluster else "" 1400 where = self.sql(expression, "where") 1401 returning = self.sql(expression, "returning") 1402 limit = self.sql(expression, "limit") 1403 tables = self.expressions(expression, key="tables") 1404 tables = f" {tables}" if tables else "" 1405 if self.RETURNING_END: 1406 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1407 else: 1408 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1409 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1410 1411 def drop_sql(self, expression: exp.Drop) -> str: 1412 this = self.sql(expression, "this") 1413 expressions = self.expressions(expression, flat=True) 1414 expressions = f" ({expressions})" if expressions else "" 1415 kind = expression.args["kind"] 1416 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1417 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1418 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1419 on_cluster = self.sql(expression, "cluster") 1420 on_cluster = f" {on_cluster}" if on_cluster else "" 1421 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1422 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1423 cascade = " CASCADE" if expression.args.get("cascade") else "" 1424 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1425 purge = " PURGE" if expression.args.get("purge") else "" 1426 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1427 1428 def set_operation(self, expression: exp.SetOperation) -> str: 1429 op_type = type(expression) 1430 op_name = op_type.key.upper() 1431 1432 distinct = expression.args.get("distinct") 1433 if ( 1434 distinct is False 1435 and op_type in (exp.Except, exp.Intersect) 1436 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1437 ): 1438 self.unsupported(f"{op_name} ALL is not supported") 1439 1440 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1441 1442 if distinct is None: 1443 distinct = default_distinct 1444 if distinct is None: 1445 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1446 1447 if distinct is default_distinct: 1448 distinct_or_all = "" 1449 else: 1450 distinct_or_all = " DISTINCT" if distinct else " ALL" 1451 1452 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1453 side_kind = f"{side_kind} " if side_kind else "" 1454 1455 by_name = " BY NAME" if expression.args.get("by_name") else "" 1456 on = self.expressions(expression, key="on", flat=True) 1457 on = f" ON ({on})" if on else "" 1458 1459 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1460 1461 def set_operations(self, expression: exp.SetOperation) -> str: 1462 if not self.SET_OP_MODIFIERS: 1463 limit = expression.args.get("limit") 1464 order = expression.args.get("order") 1465 1466 if limit or order: 1467 select = self._move_ctes_to_top_level( 1468 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1469 ) 1470 1471 if limit: 1472 select = select.limit(limit.pop(), copy=False) 1473 if order: 1474 select = select.order_by(order.pop(), copy=False) 1475 return self.sql(select) 1476 1477 sqls: t.List[str] = [] 1478 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1479 1480 while stack: 1481 node = stack.pop() 1482 1483 if isinstance(node, exp.SetOperation): 1484 stack.append(node.expression) 1485 stack.append( 1486 self.maybe_comment( 1487 self.set_operation(node), comments=node.comments, separated=True 1488 ) 1489 ) 1490 stack.append(node.this) 1491 else: 1492 sqls.append(self.sql(node)) 1493 1494 this = self.sep().join(sqls) 1495 this = self.query_modifiers(expression, this) 1496 return self.prepend_ctes(expression, this) 1497 1498 def fetch_sql(self, expression: exp.Fetch) -> str: 1499 direction = expression.args.get("direction") 1500 direction = f" {direction}" if direction else "" 1501 count = self.sql(expression, "count") 1502 count = f" {count}" if count else "" 1503 limit_options = self.sql(expression, "limit_options") 1504 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1505 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1506 1507 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1508 percent = " PERCENT" if expression.args.get("percent") else "" 1509 rows = " ROWS" if expression.args.get("rows") else "" 1510 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1511 if not with_ties and rows: 1512 with_ties = " ONLY" 1513 return f"{percent}{rows}{with_ties}" 1514 1515 def filter_sql(self, expression: exp.Filter) -> str: 1516 if self.AGGREGATE_FILTER_SUPPORTED: 1517 this = self.sql(expression, "this") 1518 where = self.sql(expression, "expression").strip() 1519 return f"{this} FILTER({where})" 1520 1521 agg = expression.this 1522 agg_arg = agg.this 1523 cond = expression.expression.this 1524 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1525 return self.sql(agg) 1526 1527 def hint_sql(self, expression: exp.Hint) -> str: 1528 if not self.QUERY_HINTS: 1529 self.unsupported("Hints are not supported") 1530 return "" 1531 1532 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1533 1534 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1535 using = self.sql(expression, "using") 1536 using = f" USING {using}" if using else "" 1537 columns = self.expressions(expression, key="columns", flat=True) 1538 columns = f"({columns})" if columns else "" 1539 partition_by = self.expressions(expression, key="partition_by", flat=True) 1540 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1541 where = self.sql(expression, "where") 1542 include = self.expressions(expression, key="include", flat=True) 1543 if include: 1544 include = f" INCLUDE ({include})" 1545 with_storage = self.expressions(expression, key="with_storage", flat=True) 1546 with_storage = f" WITH ({with_storage})" if with_storage else "" 1547 tablespace = self.sql(expression, "tablespace") 1548 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1549 on = self.sql(expression, "on") 1550 on = f" ON {on}" if on else "" 1551 1552 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1553 1554 def index_sql(self, expression: exp.Index) -> str: 1555 unique = "UNIQUE " if expression.args.get("unique") else "" 1556 primary = "PRIMARY " if expression.args.get("primary") else "" 1557 amp = "AMP " if expression.args.get("amp") else "" 1558 name = self.sql(expression, "this") 1559 name = f"{name} " if name else "" 1560 table = self.sql(expression, "table") 1561 table = f"{self.INDEX_ON} {table}" if table else "" 1562 1563 index = "INDEX " if not table else "" 1564 1565 params = self.sql(expression, "params") 1566 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1567 1568 def identifier_sql(self, expression: exp.Identifier) -> str: 1569 text = expression.name 1570 lower = text.lower() 1571 text = lower if self.normalize and not expression.quoted else text 1572 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1573 if ( 1574 expression.quoted 1575 or self.dialect.can_identify(text, self.identify) 1576 or lower in self.RESERVED_KEYWORDS 1577 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1578 ): 1579 text = f"{self._identifier_start}{text}{self._identifier_end}" 1580 return text 1581 1582 def hex_sql(self, expression: exp.Hex) -> str: 1583 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1584 if self.dialect.HEX_LOWERCASE: 1585 text = self.func("LOWER", text) 1586 1587 return text 1588 1589 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1590 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1591 if not self.dialect.HEX_LOWERCASE: 1592 text = self.func("LOWER", text) 1593 return text 1594 1595 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1596 input_format = self.sql(expression, "input_format") 1597 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1598 output_format = self.sql(expression, "output_format") 1599 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1600 return self.sep().join((input_format, output_format)) 1601 1602 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1603 string = self.sql(exp.Literal.string(expression.name)) 1604 return f"{prefix}{string}" 1605 1606 def partition_sql(self, expression: exp.Partition) -> str: 1607 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1608 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1609 1610 def properties_sql(self, expression: exp.Properties) -> str: 1611 root_properties = [] 1612 with_properties = [] 1613 1614 for p in expression.expressions: 1615 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1616 if p_loc == exp.Properties.Location.POST_WITH: 1617 with_properties.append(p) 1618 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1619 root_properties.append(p) 1620 1621 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1622 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1623 1624 if root_props and with_props and not self.pretty: 1625 with_props = " " + with_props 1626 1627 return root_props + with_props 1628 1629 def root_properties(self, properties: exp.Properties) -> str: 1630 if properties.expressions: 1631 return self.expressions(properties, indent=False, sep=" ") 1632 return "" 1633 1634 def properties( 1635 self, 1636 properties: exp.Properties, 1637 prefix: str = "", 1638 sep: str = ", ", 1639 suffix: str = "", 1640 wrapped: bool = True, 1641 ) -> str: 1642 if properties.expressions: 1643 expressions = self.expressions(properties, sep=sep, indent=False) 1644 if expressions: 1645 expressions = self.wrap(expressions) if wrapped else expressions 1646 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1647 return "" 1648 1649 def with_properties(self, properties: exp.Properties) -> str: 1650 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1651 1652 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1653 properties_locs = defaultdict(list) 1654 for p in properties.expressions: 1655 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1656 if p_loc != exp.Properties.Location.UNSUPPORTED: 1657 properties_locs[p_loc].append(p) 1658 else: 1659 self.unsupported(f"Unsupported property {p.key}") 1660 1661 return properties_locs 1662 1663 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1664 if isinstance(expression.this, exp.Dot): 1665 return self.sql(expression, "this") 1666 return f"'{expression.name}'" if string_key else expression.name 1667 1668 def property_sql(self, expression: exp.Property) -> str: 1669 property_cls = expression.__class__ 1670 if property_cls == exp.Property: 1671 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1672 1673 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1674 if not property_name: 1675 self.unsupported(f"Unsupported property {expression.key}") 1676 1677 return f"{property_name}={self.sql(expression, 'this')}" 1678 1679 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1680 if self.SUPPORTS_CREATE_TABLE_LIKE: 1681 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1682 options = f" {options}" if options else "" 1683 1684 like = f"LIKE {self.sql(expression, 'this')}{options}" 1685 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1686 like = f"({like})" 1687 1688 return like 1689 1690 if expression.expressions: 1691 self.unsupported("Transpilation of LIKE property options is unsupported") 1692 1693 select = exp.select("*").from_(expression.this).limit(0) 1694 return f"AS {self.sql(select)}" 1695 1696 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1697 no = "NO " if expression.args.get("no") else "" 1698 protection = " PROTECTION" if expression.args.get("protection") else "" 1699 return f"{no}FALLBACK{protection}" 1700 1701 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1702 no = "NO " if expression.args.get("no") else "" 1703 local = expression.args.get("local") 1704 local = f"{local} " if local else "" 1705 dual = "DUAL " if expression.args.get("dual") else "" 1706 before = "BEFORE " if expression.args.get("before") else "" 1707 after = "AFTER " if expression.args.get("after") else "" 1708 return f"{no}{local}{dual}{before}{after}JOURNAL" 1709 1710 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1711 freespace = self.sql(expression, "this") 1712 percent = " PERCENT" if expression.args.get("percent") else "" 1713 return f"FREESPACE={freespace}{percent}" 1714 1715 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1716 if expression.args.get("default"): 1717 property = "DEFAULT" 1718 elif expression.args.get("on"): 1719 property = "ON" 1720 else: 1721 property = "OFF" 1722 return f"CHECKSUM={property}" 1723 1724 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1725 if expression.args.get("no"): 1726 return "NO MERGEBLOCKRATIO" 1727 if expression.args.get("default"): 1728 return "DEFAULT MERGEBLOCKRATIO" 1729 1730 percent = " PERCENT" if expression.args.get("percent") else "" 1731 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1732 1733 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1734 default = expression.args.get("default") 1735 minimum = expression.args.get("minimum") 1736 maximum = expression.args.get("maximum") 1737 if default or minimum or maximum: 1738 if default: 1739 prop = "DEFAULT" 1740 elif minimum: 1741 prop = "MINIMUM" 1742 else: 1743 prop = "MAXIMUM" 1744 return f"{prop} DATABLOCKSIZE" 1745 units = expression.args.get("units") 1746 units = f" {units}" if units else "" 1747 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1748 1749 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1750 autotemp = expression.args.get("autotemp") 1751 always = expression.args.get("always") 1752 default = expression.args.get("default") 1753 manual = expression.args.get("manual") 1754 never = expression.args.get("never") 1755 1756 if autotemp is not None: 1757 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1758 elif always: 1759 prop = "ALWAYS" 1760 elif default: 1761 prop = "DEFAULT" 1762 elif manual: 1763 prop = "MANUAL" 1764 elif never: 1765 prop = "NEVER" 1766 return f"BLOCKCOMPRESSION={prop}" 1767 1768 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1769 no = expression.args.get("no") 1770 no = " NO" if no else "" 1771 concurrent = expression.args.get("concurrent") 1772 concurrent = " CONCURRENT" if concurrent else "" 1773 target = self.sql(expression, "target") 1774 target = f" {target}" if target else "" 1775 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1776 1777 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1778 if isinstance(expression.this, list): 1779 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1780 if expression.this: 1781 modulus = self.sql(expression, "this") 1782 remainder = self.sql(expression, "expression") 1783 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1784 1785 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1786 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1787 return f"FROM ({from_expressions}) TO ({to_expressions})" 1788 1789 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1790 this = self.sql(expression, "this") 1791 1792 for_values_or_default = expression.expression 1793 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1794 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1795 else: 1796 for_values_or_default = " DEFAULT" 1797 1798 return f"PARTITION OF {this}{for_values_or_default}" 1799 1800 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1801 kind = expression.args.get("kind") 1802 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1803 for_or_in = expression.args.get("for_or_in") 1804 for_or_in = f" {for_or_in}" if for_or_in else "" 1805 lock_type = expression.args.get("lock_type") 1806 override = " OVERRIDE" if expression.args.get("override") else "" 1807 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1808 1809 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1810 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1811 statistics = expression.args.get("statistics") 1812 statistics_sql = "" 1813 if statistics is not None: 1814 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1815 return f"{data_sql}{statistics_sql}" 1816 1817 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1818 this = self.sql(expression, "this") 1819 this = f"HISTORY_TABLE={this}" if this else "" 1820 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1821 data_consistency = ( 1822 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1823 ) 1824 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1825 retention_period = ( 1826 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1827 ) 1828 1829 if this: 1830 on_sql = self.func("ON", this, data_consistency, retention_period) 1831 else: 1832 on_sql = "ON" if expression.args.get("on") else "OFF" 1833 1834 sql = f"SYSTEM_VERSIONING={on_sql}" 1835 1836 return f"WITH({sql})" if expression.args.get("with") else sql 1837 1838 def insert_sql(self, expression: exp.Insert) -> str: 1839 hint = self.sql(expression, "hint") 1840 overwrite = expression.args.get("overwrite") 1841 1842 if isinstance(expression.this, exp.Directory): 1843 this = " OVERWRITE" if overwrite else " INTO" 1844 else: 1845 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1846 1847 stored = self.sql(expression, "stored") 1848 stored = f" {stored}" if stored else "" 1849 alternative = expression.args.get("alternative") 1850 alternative = f" OR {alternative}" if alternative else "" 1851 ignore = " IGNORE" if expression.args.get("ignore") else "" 1852 is_function = expression.args.get("is_function") 1853 if is_function: 1854 this = f"{this} FUNCTION" 1855 this = f"{this} {self.sql(expression, 'this')}" 1856 1857 exists = " IF EXISTS" if expression.args.get("exists") else "" 1858 where = self.sql(expression, "where") 1859 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1860 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1861 on_conflict = self.sql(expression, "conflict") 1862 on_conflict = f" {on_conflict}" if on_conflict else "" 1863 by_name = " BY NAME" if expression.args.get("by_name") else "" 1864 returning = self.sql(expression, "returning") 1865 1866 if self.RETURNING_END: 1867 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1868 else: 1869 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1870 1871 partition_by = self.sql(expression, "partition") 1872 partition_by = f" {partition_by}" if partition_by else "" 1873 settings = self.sql(expression, "settings") 1874 settings = f" {settings}" if settings else "" 1875 1876 source = self.sql(expression, "source") 1877 source = f"TABLE {source}" if source else "" 1878 1879 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1880 return self.prepend_ctes(expression, sql) 1881 1882 def introducer_sql(self, expression: exp.Introducer) -> str: 1883 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1884 1885 def kill_sql(self, expression: exp.Kill) -> str: 1886 kind = self.sql(expression, "kind") 1887 kind = f" {kind}" if kind else "" 1888 this = self.sql(expression, "this") 1889 this = f" {this}" if this else "" 1890 return f"KILL{kind}{this}" 1891 1892 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1893 return expression.name 1894 1895 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1896 return expression.name 1897 1898 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1899 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1900 1901 constraint = self.sql(expression, "constraint") 1902 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1903 1904 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1905 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1906 action = self.sql(expression, "action") 1907 1908 expressions = self.expressions(expression, flat=True) 1909 if expressions: 1910 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1911 expressions = f" {set_keyword}{expressions}" 1912 1913 where = self.sql(expression, "where") 1914 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1915 1916 def returning_sql(self, expression: exp.Returning) -> str: 1917 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1918 1919 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1920 fields = self.sql(expression, "fields") 1921 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1922 escaped = self.sql(expression, "escaped") 1923 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1924 items = self.sql(expression, "collection_items") 1925 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1926 keys = self.sql(expression, "map_keys") 1927 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1928 lines = self.sql(expression, "lines") 1929 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1930 null = self.sql(expression, "null") 1931 null = f" NULL DEFINED AS {null}" if null else "" 1932 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1933 1934 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1935 return f"WITH ({self.expressions(expression, flat=True)})" 1936 1937 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1938 this = f"{self.sql(expression, 'this')} INDEX" 1939 target = self.sql(expression, "target") 1940 target = f" FOR {target}" if target else "" 1941 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1942 1943 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1944 this = self.sql(expression, "this") 1945 kind = self.sql(expression, "kind") 1946 expr = self.sql(expression, "expression") 1947 return f"{this} ({kind} => {expr})" 1948 1949 def table_parts(self, expression: exp.Table) -> str: 1950 return ".".join( 1951 self.sql(part) 1952 for part in ( 1953 expression.args.get("catalog"), 1954 expression.args.get("db"), 1955 expression.args.get("this"), 1956 ) 1957 if part is not None 1958 ) 1959 1960 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1961 table = self.table_parts(expression) 1962 only = "ONLY " if expression.args.get("only") else "" 1963 partition = self.sql(expression, "partition") 1964 partition = f" {partition}" if partition else "" 1965 version = self.sql(expression, "version") 1966 version = f" {version}" if version else "" 1967 alias = self.sql(expression, "alias") 1968 alias = f"{sep}{alias}" if alias else "" 1969 1970 sample = self.sql(expression, "sample") 1971 if self.dialect.ALIAS_POST_TABLESAMPLE: 1972 sample_pre_alias = sample 1973 sample_post_alias = "" 1974 else: 1975 sample_pre_alias = "" 1976 sample_post_alias = sample 1977 1978 hints = self.expressions(expression, key="hints", sep=" ") 1979 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1980 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1981 joins = self.indent( 1982 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1983 ) 1984 laterals = self.expressions(expression, key="laterals", sep="") 1985 1986 file_format = self.sql(expression, "format") 1987 if file_format: 1988 pattern = self.sql(expression, "pattern") 1989 pattern = f", PATTERN => {pattern}" if pattern else "" 1990 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1991 1992 ordinality = expression.args.get("ordinality") or "" 1993 if ordinality: 1994 ordinality = f" WITH ORDINALITY{alias}" 1995 alias = "" 1996 1997 when = self.sql(expression, "when") 1998 if when: 1999 table = f"{table} {when}" 2000 2001 changes = self.sql(expression, "changes") 2002 changes = f" {changes}" if changes else "" 2003 2004 rows_from = self.expressions(expression, key="rows_from") 2005 if rows_from: 2006 table = f"ROWS FROM {self.wrap(rows_from)}" 2007 2008 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2009 2010 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2011 table = self.func("TABLE", expression.this) 2012 alias = self.sql(expression, "alias") 2013 alias = f" AS {alias}" if alias else "" 2014 sample = self.sql(expression, "sample") 2015 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2016 joins = self.indent( 2017 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2018 ) 2019 return f"{table}{alias}{pivots}{sample}{joins}" 2020 2021 def tablesample_sql( 2022 self, 2023 expression: exp.TableSample, 2024 tablesample_keyword: t.Optional[str] = None, 2025 ) -> str: 2026 method = self.sql(expression, "method") 2027 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2028 numerator = self.sql(expression, "bucket_numerator") 2029 denominator = self.sql(expression, "bucket_denominator") 2030 field = self.sql(expression, "bucket_field") 2031 field = f" ON {field}" if field else "" 2032 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2033 seed = self.sql(expression, "seed") 2034 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2035 2036 size = self.sql(expression, "size") 2037 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2038 size = f"{size} ROWS" 2039 2040 percent = self.sql(expression, "percent") 2041 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2042 percent = f"{percent} PERCENT" 2043 2044 expr = f"{bucket}{percent}{size}" 2045 if self.TABLESAMPLE_REQUIRES_PARENS: 2046 expr = f"({expr})" 2047 2048 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2049 2050 def pivot_sql(self, expression: exp.Pivot) -> str: 2051 expressions = self.expressions(expression, flat=True) 2052 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2053 2054 group = self.sql(expression, "group") 2055 2056 if expression.this: 2057 this = self.sql(expression, "this") 2058 if not expressions: 2059 return f"UNPIVOT {this}" 2060 2061 on = f"{self.seg('ON')} {expressions}" 2062 into = self.sql(expression, "into") 2063 into = f"{self.seg('INTO')} {into}" if into else "" 2064 using = self.expressions(expression, key="using", flat=True) 2065 using = f"{self.seg('USING')} {using}" if using else "" 2066 return f"{direction} {this}{on}{into}{using}{group}" 2067 2068 alias = self.sql(expression, "alias") 2069 alias = f" AS {alias}" if alias else "" 2070 2071 fields = self.expressions( 2072 expression, 2073 "fields", 2074 sep=" ", 2075 dynamic=True, 2076 new_line=True, 2077 skip_first=True, 2078 skip_last=True, 2079 ) 2080 2081 include_nulls = expression.args.get("include_nulls") 2082 if include_nulls is not None: 2083 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2084 else: 2085 nulls = "" 2086 2087 default_on_null = self.sql(expression, "default_on_null") 2088 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2089 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2090 2091 def version_sql(self, expression: exp.Version) -> str: 2092 this = f"FOR {expression.name}" 2093 kind = expression.text("kind") 2094 expr = self.sql(expression, "expression") 2095 return f"{this} {kind} {expr}" 2096 2097 def tuple_sql(self, expression: exp.Tuple) -> str: 2098 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2099 2100 def update_sql(self, expression: exp.Update) -> str: 2101 this = self.sql(expression, "this") 2102 set_sql = self.expressions(expression, flat=True) 2103 from_sql = self.sql(expression, "from") 2104 where_sql = self.sql(expression, "where") 2105 returning = self.sql(expression, "returning") 2106 order = self.sql(expression, "order") 2107 limit = self.sql(expression, "limit") 2108 if self.RETURNING_END: 2109 expression_sql = f"{from_sql}{where_sql}{returning}" 2110 else: 2111 expression_sql = f"{returning}{from_sql}{where_sql}" 2112 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2113 return self.prepend_ctes(expression, sql) 2114 2115 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2116 values_as_table = values_as_table and self.VALUES_AS_TABLE 2117 2118 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2119 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2120 args = self.expressions(expression) 2121 alias = self.sql(expression, "alias") 2122 values = f"VALUES{self.seg('')}{args}" 2123 values = ( 2124 f"({values})" 2125 if self.WRAP_DERIVED_VALUES 2126 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2127 else values 2128 ) 2129 return f"{values} AS {alias}" if alias else values 2130 2131 # Converts `VALUES...` expression into a series of select unions. 2132 alias_node = expression.args.get("alias") 2133 column_names = alias_node and alias_node.columns 2134 2135 selects: t.List[exp.Query] = [] 2136 2137 for i, tup in enumerate(expression.expressions): 2138 row = tup.expressions 2139 2140 if i == 0 and column_names: 2141 row = [ 2142 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2143 ] 2144 2145 selects.append(exp.Select(expressions=row)) 2146 2147 if self.pretty: 2148 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2149 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2150 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2151 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2152 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2153 2154 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2155 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2156 return f"({unions}){alias}" 2157 2158 def var_sql(self, expression: exp.Var) -> str: 2159 return self.sql(expression, "this") 2160 2161 @unsupported_args("expressions") 2162 def into_sql(self, expression: exp.Into) -> str: 2163 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2164 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2165 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2166 2167 def from_sql(self, expression: exp.From) -> str: 2168 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2169 2170 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2171 grouping_sets = self.expressions(expression, indent=False) 2172 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2173 2174 def rollup_sql(self, expression: exp.Rollup) -> str: 2175 expressions = self.expressions(expression, indent=False) 2176 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2177 2178 def cube_sql(self, expression: exp.Cube) -> str: 2179 expressions = self.expressions(expression, indent=False) 2180 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2181 2182 def group_sql(self, expression: exp.Group) -> str: 2183 group_by_all = expression.args.get("all") 2184 if group_by_all is True: 2185 modifier = " ALL" 2186 elif group_by_all is False: 2187 modifier = " DISTINCT" 2188 else: 2189 modifier = "" 2190 2191 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2192 2193 grouping_sets = self.expressions(expression, key="grouping_sets") 2194 cube = self.expressions(expression, key="cube") 2195 rollup = self.expressions(expression, key="rollup") 2196 2197 groupings = csv( 2198 self.seg(grouping_sets) if grouping_sets else "", 2199 self.seg(cube) if cube else "", 2200 self.seg(rollup) if rollup else "", 2201 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2202 sep=self.GROUPINGS_SEP, 2203 ) 2204 2205 if ( 2206 expression.expressions 2207 and groupings 2208 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2209 ): 2210 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2211 2212 return f"{group_by}{groupings}" 2213 2214 def having_sql(self, expression: exp.Having) -> str: 2215 this = self.indent(self.sql(expression, "this")) 2216 return f"{self.seg('HAVING')}{self.sep()}{this}" 2217 2218 def connect_sql(self, expression: exp.Connect) -> str: 2219 start = self.sql(expression, "start") 2220 start = self.seg(f"START WITH {start}") if start else "" 2221 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2222 connect = self.sql(expression, "connect") 2223 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2224 return start + connect 2225 2226 def prior_sql(self, expression: exp.Prior) -> str: 2227 return f"PRIOR {self.sql(expression, 'this')}" 2228 2229 def join_sql(self, expression: exp.Join) -> str: 2230 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2231 side = None 2232 else: 2233 side = expression.side 2234 2235 op_sql = " ".join( 2236 op 2237 for op in ( 2238 expression.method, 2239 "GLOBAL" if expression.args.get("global") else None, 2240 side, 2241 expression.kind, 2242 expression.hint if self.JOIN_HINTS else None, 2243 ) 2244 if op 2245 ) 2246 match_cond = self.sql(expression, "match_condition") 2247 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2248 on_sql = self.sql(expression, "on") 2249 using = expression.args.get("using") 2250 2251 if not on_sql and using: 2252 on_sql = csv(*(self.sql(column) for column in using)) 2253 2254 this = expression.this 2255 this_sql = self.sql(this) 2256 2257 exprs = self.expressions(expression) 2258 if exprs: 2259 this_sql = f"{this_sql},{self.seg(exprs)}" 2260 2261 if on_sql: 2262 on_sql = self.indent(on_sql, skip_first=True) 2263 space = self.seg(" " * self.pad) if self.pretty else " " 2264 if using: 2265 on_sql = f"{space}USING ({on_sql})" 2266 else: 2267 on_sql = f"{space}ON {on_sql}" 2268 elif not op_sql: 2269 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2270 return f" {this_sql}" 2271 2272 return f", {this_sql}" 2273 2274 if op_sql != "STRAIGHT_JOIN": 2275 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2276 2277 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2278 2279 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2280 args = self.expressions(expression, flat=True) 2281 args = f"({args})" if len(args.split(",")) > 1 else args 2282 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2283 2284 def lateral_op(self, expression: exp.Lateral) -> str: 2285 cross_apply = expression.args.get("cross_apply") 2286 2287 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2288 if cross_apply is True: 2289 op = "INNER JOIN " 2290 elif cross_apply is False: 2291 op = "LEFT JOIN " 2292 else: 2293 op = "" 2294 2295 return f"{op}LATERAL" 2296 2297 def lateral_sql(self, expression: exp.Lateral) -> str: 2298 this = self.sql(expression, "this") 2299 2300 if expression.args.get("view"): 2301 alias = expression.args["alias"] 2302 columns = self.expressions(alias, key="columns", flat=True) 2303 table = f" {alias.name}" if alias.name else "" 2304 columns = f" AS {columns}" if columns else "" 2305 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2306 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2307 2308 alias = self.sql(expression, "alias") 2309 alias = f" AS {alias}" if alias else "" 2310 2311 ordinality = expression.args.get("ordinality") or "" 2312 if ordinality: 2313 ordinality = f" WITH ORDINALITY{alias}" 2314 alias = "" 2315 2316 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2317 2318 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2319 this = self.sql(expression, "this") 2320 2321 args = [ 2322 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2323 for e in (expression.args.get(k) for k in ("offset", "expression")) 2324 if e 2325 ] 2326 2327 args_sql = ", ".join(self.sql(e) for e in args) 2328 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2329 expressions = self.expressions(expression, flat=True) 2330 limit_options = self.sql(expression, "limit_options") 2331 expressions = f" BY {expressions}" if expressions else "" 2332 2333 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2334 2335 def offset_sql(self, expression: exp.Offset) -> str: 2336 this = self.sql(expression, "this") 2337 value = expression.expression 2338 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2339 expressions = self.expressions(expression, flat=True) 2340 expressions = f" BY {expressions}" if expressions else "" 2341 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2342 2343 def setitem_sql(self, expression: exp.SetItem) -> str: 2344 kind = self.sql(expression, "kind") 2345 kind = f"{kind} " if kind else "" 2346 this = self.sql(expression, "this") 2347 expressions = self.expressions(expression) 2348 collate = self.sql(expression, "collate") 2349 collate = f" COLLATE {collate}" if collate else "" 2350 global_ = "GLOBAL " if expression.args.get("global") else "" 2351 return f"{global_}{kind}{this}{expressions}{collate}" 2352 2353 def set_sql(self, expression: exp.Set) -> str: 2354 expressions = f" {self.expressions(expression, flat=True)}" 2355 tag = " TAG" if expression.args.get("tag") else "" 2356 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2357 2358 def pragma_sql(self, expression: exp.Pragma) -> str: 2359 return f"PRAGMA {self.sql(expression, 'this')}" 2360 2361 def lock_sql(self, expression: exp.Lock) -> str: 2362 if not self.LOCKING_READS_SUPPORTED: 2363 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2364 return "" 2365 2366 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2367 expressions = self.expressions(expression, flat=True) 2368 expressions = f" OF {expressions}" if expressions else "" 2369 wait = expression.args.get("wait") 2370 2371 if wait is not None: 2372 if isinstance(wait, exp.Literal): 2373 wait = f" WAIT {self.sql(wait)}" 2374 else: 2375 wait = " NOWAIT" if wait else " SKIP LOCKED" 2376 2377 return f"{lock_type}{expressions}{wait or ''}" 2378 2379 def literal_sql(self, expression: exp.Literal) -> str: 2380 text = expression.this or "" 2381 if expression.is_string: 2382 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2383 return text 2384 2385 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2386 if self.dialect.ESCAPED_SEQUENCES: 2387 to_escaped = self.dialect.ESCAPED_SEQUENCES 2388 text = "".join( 2389 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2390 ) 2391 2392 return self._replace_line_breaks(text).replace( 2393 self.dialect.QUOTE_END, self._escaped_quote_end 2394 ) 2395 2396 def loaddata_sql(self, expression: exp.LoadData) -> str: 2397 local = " LOCAL" if expression.args.get("local") else "" 2398 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2399 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2400 this = f" INTO TABLE {self.sql(expression, 'this')}" 2401 partition = self.sql(expression, "partition") 2402 partition = f" {partition}" if partition else "" 2403 input_format = self.sql(expression, "input_format") 2404 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2405 serde = self.sql(expression, "serde") 2406 serde = f" SERDE {serde}" if serde else "" 2407 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2408 2409 def null_sql(self, *_) -> str: 2410 return "NULL" 2411 2412 def boolean_sql(self, expression: exp.Boolean) -> str: 2413 return "TRUE" if expression.this else "FALSE" 2414 2415 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2416 this = self.sql(expression, "this") 2417 this = f"{this} " if this else this 2418 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2419 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2420 2421 def withfill_sql(self, expression: exp.WithFill) -> str: 2422 from_sql = self.sql(expression, "from") 2423 from_sql = f" FROM {from_sql}" if from_sql else "" 2424 to_sql = self.sql(expression, "to") 2425 to_sql = f" TO {to_sql}" if to_sql else "" 2426 step_sql = self.sql(expression, "step") 2427 step_sql = f" STEP {step_sql}" if step_sql else "" 2428 interpolated_values = [ 2429 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2430 if isinstance(e, exp.Alias) 2431 else self.sql(e, "this") 2432 for e in expression.args.get("interpolate") or [] 2433 ] 2434 interpolate = ( 2435 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2436 ) 2437 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2438 2439 def cluster_sql(self, expression: exp.Cluster) -> str: 2440 return self.op_expressions("CLUSTER BY", expression) 2441 2442 def distribute_sql(self, expression: exp.Distribute) -> str: 2443 return self.op_expressions("DISTRIBUTE BY", expression) 2444 2445 def sort_sql(self, expression: exp.Sort) -> str: 2446 return self.op_expressions("SORT BY", expression) 2447 2448 def ordered_sql(self, expression: exp.Ordered) -> str: 2449 desc = expression.args.get("desc") 2450 asc = not desc 2451 2452 nulls_first = expression.args.get("nulls_first") 2453 nulls_last = not nulls_first 2454 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2455 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2456 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2457 2458 this = self.sql(expression, "this") 2459 2460 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2461 nulls_sort_change = "" 2462 if nulls_first and ( 2463 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2464 ): 2465 nulls_sort_change = " NULLS FIRST" 2466 elif ( 2467 nulls_last 2468 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2469 and not nulls_are_last 2470 ): 2471 nulls_sort_change = " NULLS LAST" 2472 2473 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2474 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2475 window = expression.find_ancestor(exp.Window, exp.Select) 2476 if isinstance(window, exp.Window) and window.args.get("spec"): 2477 self.unsupported( 2478 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2479 ) 2480 nulls_sort_change = "" 2481 elif self.NULL_ORDERING_SUPPORTED is False and ( 2482 (asc and nulls_sort_change == " NULLS LAST") 2483 or (desc and nulls_sort_change == " NULLS FIRST") 2484 ): 2485 # BigQuery does not allow these ordering/nulls combinations when used under 2486 # an aggregation func or under a window containing one 2487 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2488 2489 if isinstance(ancestor, exp.Window): 2490 ancestor = ancestor.this 2491 if isinstance(ancestor, exp.AggFunc): 2492 self.unsupported( 2493 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2494 ) 2495 nulls_sort_change = "" 2496 elif self.NULL_ORDERING_SUPPORTED is None: 2497 if expression.this.is_int: 2498 self.unsupported( 2499 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2500 ) 2501 elif not isinstance(expression.this, exp.Rand): 2502 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2503 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2504 nulls_sort_change = "" 2505 2506 with_fill = self.sql(expression, "with_fill") 2507 with_fill = f" {with_fill}" if with_fill else "" 2508 2509 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2510 2511 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2512 window_frame = self.sql(expression, "window_frame") 2513 window_frame = f"{window_frame} " if window_frame else "" 2514 2515 this = self.sql(expression, "this") 2516 2517 return f"{window_frame}{this}" 2518 2519 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2520 partition = self.partition_by_sql(expression) 2521 order = self.sql(expression, "order") 2522 measures = self.expressions(expression, key="measures") 2523 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2524 rows = self.sql(expression, "rows") 2525 rows = self.seg(rows) if rows else "" 2526 after = self.sql(expression, "after") 2527 after = self.seg(after) if after else "" 2528 pattern = self.sql(expression, "pattern") 2529 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2530 definition_sqls = [ 2531 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2532 for definition in expression.args.get("define", []) 2533 ] 2534 definitions = self.expressions(sqls=definition_sqls) 2535 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2536 body = "".join( 2537 ( 2538 partition, 2539 order, 2540 measures, 2541 rows, 2542 after, 2543 pattern, 2544 define, 2545 ) 2546 ) 2547 alias = self.sql(expression, "alias") 2548 alias = f" {alias}" if alias else "" 2549 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2550 2551 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2552 limit = expression.args.get("limit") 2553 2554 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2555 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2556 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2557 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2558 2559 return csv( 2560 *sqls, 2561 *[self.sql(join) for join in expression.args.get("joins") or []], 2562 self.sql(expression, "match"), 2563 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2564 self.sql(expression, "prewhere"), 2565 self.sql(expression, "where"), 2566 self.sql(expression, "connect"), 2567 self.sql(expression, "group"), 2568 self.sql(expression, "having"), 2569 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2570 self.sql(expression, "order"), 2571 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2572 *self.after_limit_modifiers(expression), 2573 self.options_modifier(expression), 2574 sep="", 2575 ) 2576 2577 def options_modifier(self, expression: exp.Expression) -> str: 2578 options = self.expressions(expression, key="options") 2579 return f" {options}" if options else "" 2580 2581 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2582 return "" 2583 2584 def offset_limit_modifiers( 2585 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2586 ) -> t.List[str]: 2587 return [ 2588 self.sql(expression, "offset") if fetch else self.sql(limit), 2589 self.sql(limit) if fetch else self.sql(expression, "offset"), 2590 ] 2591 2592 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2593 locks = self.expressions(expression, key="locks", sep=" ") 2594 locks = f" {locks}" if locks else "" 2595 return [locks, self.sql(expression, "sample")] 2596 2597 def select_sql(self, expression: exp.Select) -> str: 2598 into = expression.args.get("into") 2599 if not self.SUPPORTS_SELECT_INTO and into: 2600 into.pop() 2601 2602 hint = self.sql(expression, "hint") 2603 distinct = self.sql(expression, "distinct") 2604 distinct = f" {distinct}" if distinct else "" 2605 kind = self.sql(expression, "kind") 2606 2607 limit = expression.args.get("limit") 2608 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2609 top = self.limit_sql(limit, top=True) 2610 limit.pop() 2611 else: 2612 top = "" 2613 2614 expressions = self.expressions(expression) 2615 2616 if kind: 2617 if kind in self.SELECT_KINDS: 2618 kind = f" AS {kind}" 2619 else: 2620 if kind == "STRUCT": 2621 expressions = self.expressions( 2622 sqls=[ 2623 self.sql( 2624 exp.Struct( 2625 expressions=[ 2626 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2627 if isinstance(e, exp.Alias) 2628 else e 2629 for e in expression.expressions 2630 ] 2631 ) 2632 ) 2633 ] 2634 ) 2635 kind = "" 2636 2637 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2638 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2639 2640 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2641 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2642 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2643 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2644 sql = self.query_modifiers( 2645 expression, 2646 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2647 self.sql(expression, "into", comment=False), 2648 self.sql(expression, "from", comment=False), 2649 ) 2650 2651 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2652 if expression.args.get("with"): 2653 sql = self.maybe_comment(sql, expression) 2654 expression.pop_comments() 2655 2656 sql = self.prepend_ctes(expression, sql) 2657 2658 if not self.SUPPORTS_SELECT_INTO and into: 2659 if into.args.get("temporary"): 2660 table_kind = " TEMPORARY" 2661 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2662 table_kind = " UNLOGGED" 2663 else: 2664 table_kind = "" 2665 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2666 2667 return sql 2668 2669 def schema_sql(self, expression: exp.Schema) -> str: 2670 this = self.sql(expression, "this") 2671 sql = self.schema_columns_sql(expression) 2672 return f"{this} {sql}" if this and sql else this or sql 2673 2674 def schema_columns_sql(self, expression: exp.Schema) -> str: 2675 if expression.expressions: 2676 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2677 return "" 2678 2679 def star_sql(self, expression: exp.Star) -> str: 2680 except_ = self.expressions(expression, key="except", flat=True) 2681 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2682 replace = self.expressions(expression, key="replace", flat=True) 2683 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2684 rename = self.expressions(expression, key="rename", flat=True) 2685 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2686 return f"*{except_}{replace}{rename}" 2687 2688 def parameter_sql(self, expression: exp.Parameter) -> str: 2689 this = self.sql(expression, "this") 2690 return f"{self.PARAMETER_TOKEN}{this}" 2691 2692 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2693 this = self.sql(expression, "this") 2694 kind = expression.text("kind") 2695 if kind: 2696 kind = f"{kind}." 2697 return f"@@{kind}{this}" 2698 2699 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2700 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2701 2702 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2703 alias = self.sql(expression, "alias") 2704 alias = f"{sep}{alias}" if alias else "" 2705 sample = self.sql(expression, "sample") 2706 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2707 alias = f"{sample}{alias}" 2708 2709 # Set to None so it's not generated again by self.query_modifiers() 2710 expression.set("sample", None) 2711 2712 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2713 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2714 return self.prepend_ctes(expression, sql) 2715 2716 def qualify_sql(self, expression: exp.Qualify) -> str: 2717 this = self.indent(self.sql(expression, "this")) 2718 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2719 2720 def unnest_sql(self, expression: exp.Unnest) -> str: 2721 args = self.expressions(expression, flat=True) 2722 2723 alias = expression.args.get("alias") 2724 offset = expression.args.get("offset") 2725 2726 if self.UNNEST_WITH_ORDINALITY: 2727 if alias and isinstance(offset, exp.Expression): 2728 alias.append("columns", offset) 2729 2730 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2731 columns = alias.columns 2732 alias = self.sql(columns[0]) if columns else "" 2733 else: 2734 alias = self.sql(alias) 2735 2736 alias = f" AS {alias}" if alias else alias 2737 if self.UNNEST_WITH_ORDINALITY: 2738 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2739 else: 2740 if isinstance(offset, exp.Expression): 2741 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2742 elif offset: 2743 suffix = f"{alias} WITH OFFSET" 2744 else: 2745 suffix = alias 2746 2747 return f"UNNEST({args}){suffix}" 2748 2749 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2750 return "" 2751 2752 def where_sql(self, expression: exp.Where) -> str: 2753 this = self.indent(self.sql(expression, "this")) 2754 return f"{self.seg('WHERE')}{self.sep()}{this}" 2755 2756 def window_sql(self, expression: exp.Window) -> str: 2757 this = self.sql(expression, "this") 2758 partition = self.partition_by_sql(expression) 2759 order = expression.args.get("order") 2760 order = self.order_sql(order, flat=True) if order else "" 2761 spec = self.sql(expression, "spec") 2762 alias = self.sql(expression, "alias") 2763 over = self.sql(expression, "over") or "OVER" 2764 2765 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2766 2767 first = expression.args.get("first") 2768 if first is None: 2769 first = "" 2770 else: 2771 first = "FIRST" if first else "LAST" 2772 2773 if not partition and not order and not spec and alias: 2774 return f"{this} {alias}" 2775 2776 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2777 return f"{this} ({args})" 2778 2779 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2780 partition = self.expressions(expression, key="partition_by", flat=True) 2781 return f"PARTITION BY {partition}" if partition else "" 2782 2783 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2784 kind = self.sql(expression, "kind") 2785 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2786 end = ( 2787 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2788 or "CURRENT ROW" 2789 ) 2790 return f"{kind} BETWEEN {start} AND {end}" 2791 2792 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2793 this = self.sql(expression, "this") 2794 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2795 return f"{this} WITHIN GROUP ({expression_sql})" 2796 2797 def between_sql(self, expression: exp.Between) -> str: 2798 this = self.sql(expression, "this") 2799 low = self.sql(expression, "low") 2800 high = self.sql(expression, "high") 2801 return f"{this} BETWEEN {low} AND {high}" 2802 2803 def bracket_offset_expressions( 2804 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2805 ) -> t.List[exp.Expression]: 2806 return apply_index_offset( 2807 expression.this, 2808 expression.expressions, 2809 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2810 dialect=self.dialect, 2811 ) 2812 2813 def bracket_sql(self, expression: exp.Bracket) -> str: 2814 expressions = self.bracket_offset_expressions(expression) 2815 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2816 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2817 2818 def all_sql(self, expression: exp.All) -> str: 2819 return f"ALL {self.wrap(expression)}" 2820 2821 def any_sql(self, expression: exp.Any) -> str: 2822 this = self.sql(expression, "this") 2823 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2824 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2825 this = self.wrap(this) 2826 return f"ANY{this}" 2827 return f"ANY {this}" 2828 2829 def exists_sql(self, expression: exp.Exists) -> str: 2830 return f"EXISTS{self.wrap(expression)}" 2831 2832 def case_sql(self, expression: exp.Case) -> str: 2833 this = self.sql(expression, "this") 2834 statements = [f"CASE {this}" if this else "CASE"] 2835 2836 for e in expression.args["ifs"]: 2837 statements.append(f"WHEN {self.sql(e, 'this')}") 2838 statements.append(f"THEN {self.sql(e, 'true')}") 2839 2840 default = self.sql(expression, "default") 2841 2842 if default: 2843 statements.append(f"ELSE {default}") 2844 2845 statements.append("END") 2846 2847 if self.pretty and self.too_wide(statements): 2848 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2849 2850 return " ".join(statements) 2851 2852 def constraint_sql(self, expression: exp.Constraint) -> str: 2853 this = self.sql(expression, "this") 2854 expressions = self.expressions(expression, flat=True) 2855 return f"CONSTRAINT {this} {expressions}" 2856 2857 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2858 order = expression.args.get("order") 2859 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2860 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2861 2862 def extract_sql(self, expression: exp.Extract) -> str: 2863 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2864 expression_sql = self.sql(expression, "expression") 2865 return f"EXTRACT({this} FROM {expression_sql})" 2866 2867 def trim_sql(self, expression: exp.Trim) -> str: 2868 trim_type = self.sql(expression, "position") 2869 2870 if trim_type == "LEADING": 2871 func_name = "LTRIM" 2872 elif trim_type == "TRAILING": 2873 func_name = "RTRIM" 2874 else: 2875 func_name = "TRIM" 2876 2877 return self.func(func_name, expression.this, expression.expression) 2878 2879 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2880 args = expression.expressions 2881 if isinstance(expression, exp.ConcatWs): 2882 args = args[1:] # Skip the delimiter 2883 2884 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2885 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2886 2887 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2888 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2889 2890 return args 2891 2892 def concat_sql(self, expression: exp.Concat) -> str: 2893 expressions = self.convert_concat_args(expression) 2894 2895 # Some dialects don't allow a single-argument CONCAT call 2896 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2897 return self.sql(expressions[0]) 2898 2899 return self.func("CONCAT", *expressions) 2900 2901 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2902 return self.func( 2903 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2904 ) 2905 2906 def check_sql(self, expression: exp.Check) -> str: 2907 this = self.sql(expression, key="this") 2908 return f"CHECK ({this})" 2909 2910 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2911 expressions = self.expressions(expression, flat=True) 2912 expressions = f" ({expressions})" if expressions else "" 2913 reference = self.sql(expression, "reference") 2914 reference = f" {reference}" if reference else "" 2915 delete = self.sql(expression, "delete") 2916 delete = f" ON DELETE {delete}" if delete else "" 2917 update = self.sql(expression, "update") 2918 update = f" ON UPDATE {update}" if update else "" 2919 return f"FOREIGN KEY{expressions}{reference}{delete}{update}" 2920 2921 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2922 expressions = self.expressions(expression, flat=True) 2923 options = self.expressions(expression, key="options", flat=True, sep=" ") 2924 options = f" {options}" if options else "" 2925 return f"PRIMARY KEY ({expressions}){options}" 2926 2927 def if_sql(self, expression: exp.If) -> str: 2928 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2929 2930 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2931 modifier = expression.args.get("modifier") 2932 modifier = f" {modifier}" if modifier else "" 2933 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2934 2935 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2936 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2937 2938 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2939 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2940 2941 if expression.args.get("escape"): 2942 path = self.escape_str(path) 2943 2944 if self.QUOTE_JSON_PATH: 2945 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2946 2947 return path 2948 2949 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2950 if isinstance(expression, exp.JSONPathPart): 2951 transform = self.TRANSFORMS.get(expression.__class__) 2952 if not callable(transform): 2953 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2954 return "" 2955 2956 return transform(self, expression) 2957 2958 if isinstance(expression, int): 2959 return str(expression) 2960 2961 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2962 escaped = expression.replace("'", "\\'") 2963 escaped = f"\\'{expression}\\'" 2964 else: 2965 escaped = expression.replace('"', '\\"') 2966 escaped = f'"{escaped}"' 2967 2968 return escaped 2969 2970 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2971 return f"{self.sql(expression, 'this')} FORMAT JSON" 2972 2973 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2974 null_handling = expression.args.get("null_handling") 2975 null_handling = f" {null_handling}" if null_handling else "" 2976 2977 unique_keys = expression.args.get("unique_keys") 2978 if unique_keys is not None: 2979 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2980 else: 2981 unique_keys = "" 2982 2983 return_type = self.sql(expression, "return_type") 2984 return_type = f" RETURNING {return_type}" if return_type else "" 2985 encoding = self.sql(expression, "encoding") 2986 encoding = f" ENCODING {encoding}" if encoding else "" 2987 2988 return self.func( 2989 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2990 *expression.expressions, 2991 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2992 ) 2993 2994 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2995 return self.jsonobject_sql(expression) 2996 2997 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2998 null_handling = expression.args.get("null_handling") 2999 null_handling = f" {null_handling}" if null_handling else "" 3000 return_type = self.sql(expression, "return_type") 3001 return_type = f" RETURNING {return_type}" if return_type else "" 3002 strict = " STRICT" if expression.args.get("strict") else "" 3003 return self.func( 3004 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3005 ) 3006 3007 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3008 this = self.sql(expression, "this") 3009 order = self.sql(expression, "order") 3010 null_handling = expression.args.get("null_handling") 3011 null_handling = f" {null_handling}" if null_handling else "" 3012 return_type = self.sql(expression, "return_type") 3013 return_type = f" RETURNING {return_type}" if return_type else "" 3014 strict = " STRICT" if expression.args.get("strict") else "" 3015 return self.func( 3016 "JSON_ARRAYAGG", 3017 this, 3018 suffix=f"{order}{null_handling}{return_type}{strict})", 3019 ) 3020 3021 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3022 path = self.sql(expression, "path") 3023 path = f" PATH {path}" if path else "" 3024 nested_schema = self.sql(expression, "nested_schema") 3025 3026 if nested_schema: 3027 return f"NESTED{path} {nested_schema}" 3028 3029 this = self.sql(expression, "this") 3030 kind = self.sql(expression, "kind") 3031 kind = f" {kind}" if kind else "" 3032 return f"{this}{kind}{path}" 3033 3034 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3035 return self.func("COLUMNS", *expression.expressions) 3036 3037 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3038 this = self.sql(expression, "this") 3039 path = self.sql(expression, "path") 3040 path = f", {path}" if path else "" 3041 error_handling = expression.args.get("error_handling") 3042 error_handling = f" {error_handling}" if error_handling else "" 3043 empty_handling = expression.args.get("empty_handling") 3044 empty_handling = f" {empty_handling}" if empty_handling else "" 3045 schema = self.sql(expression, "schema") 3046 return self.func( 3047 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3048 ) 3049 3050 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3051 this = self.sql(expression, "this") 3052 kind = self.sql(expression, "kind") 3053 path = self.sql(expression, "path") 3054 path = f" {path}" if path else "" 3055 as_json = " AS JSON" if expression.args.get("as_json") else "" 3056 return f"{this} {kind}{path}{as_json}" 3057 3058 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3059 this = self.sql(expression, "this") 3060 path = self.sql(expression, "path") 3061 path = f", {path}" if path else "" 3062 expressions = self.expressions(expression) 3063 with_ = ( 3064 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3065 if expressions 3066 else "" 3067 ) 3068 return f"OPENJSON({this}{path}){with_}" 3069 3070 def in_sql(self, expression: exp.In) -> str: 3071 query = expression.args.get("query") 3072 unnest = expression.args.get("unnest") 3073 field = expression.args.get("field") 3074 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3075 3076 if query: 3077 in_sql = self.sql(query) 3078 elif unnest: 3079 in_sql = self.in_unnest_op(unnest) 3080 elif field: 3081 in_sql = self.sql(field) 3082 else: 3083 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3084 3085 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3086 3087 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3088 return f"(SELECT {self.sql(unnest)})" 3089 3090 def interval_sql(self, expression: exp.Interval) -> str: 3091 unit = self.sql(expression, "unit") 3092 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3093 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3094 unit = f" {unit}" if unit else "" 3095 3096 if self.SINGLE_STRING_INTERVAL: 3097 this = expression.this.name if expression.this else "" 3098 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3099 3100 this = self.sql(expression, "this") 3101 if this: 3102 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3103 this = f" {this}" if unwrapped else f" ({this})" 3104 3105 return f"INTERVAL{this}{unit}" 3106 3107 def return_sql(self, expression: exp.Return) -> str: 3108 return f"RETURN {self.sql(expression, 'this')}" 3109 3110 def reference_sql(self, expression: exp.Reference) -> str: 3111 this = self.sql(expression, "this") 3112 expressions = self.expressions(expression, flat=True) 3113 expressions = f"({expressions})" if expressions else "" 3114 options = self.expressions(expression, key="options", flat=True, sep=" ") 3115 options = f" {options}" if options else "" 3116 return f"REFERENCES {this}{expressions}{options}" 3117 3118 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3119 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3120 parent = expression.parent 3121 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3122 return self.func( 3123 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3124 ) 3125 3126 def paren_sql(self, expression: exp.Paren) -> str: 3127 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3128 return f"({sql}{self.seg(')', sep='')}" 3129 3130 def neg_sql(self, expression: exp.Neg) -> str: 3131 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3132 this_sql = self.sql(expression, "this") 3133 sep = " " if this_sql[0] == "-" else "" 3134 return f"-{sep}{this_sql}" 3135 3136 def not_sql(self, expression: exp.Not) -> str: 3137 return f"NOT {self.sql(expression, 'this')}" 3138 3139 def alias_sql(self, expression: exp.Alias) -> str: 3140 alias = self.sql(expression, "alias") 3141 alias = f" AS {alias}" if alias else "" 3142 return f"{self.sql(expression, 'this')}{alias}" 3143 3144 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3145 alias = expression.args["alias"] 3146 3147 parent = expression.parent 3148 pivot = parent and parent.parent 3149 3150 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3151 identifier_alias = isinstance(alias, exp.Identifier) 3152 literal_alias = isinstance(alias, exp.Literal) 3153 3154 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3155 alias.replace(exp.Literal.string(alias.output_name)) 3156 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3157 alias.replace(exp.to_identifier(alias.output_name)) 3158 3159 return self.alias_sql(expression) 3160 3161 def aliases_sql(self, expression: exp.Aliases) -> str: 3162 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3163 3164 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3165 this = self.sql(expression, "this") 3166 index = self.sql(expression, "expression") 3167 return f"{this} AT {index}" 3168 3169 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3170 this = self.sql(expression, "this") 3171 zone = self.sql(expression, "zone") 3172 return f"{this} AT TIME ZONE {zone}" 3173 3174 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3175 this = self.sql(expression, "this") 3176 zone = self.sql(expression, "zone") 3177 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3178 3179 def add_sql(self, expression: exp.Add) -> str: 3180 return self.binary(expression, "+") 3181 3182 def and_sql( 3183 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3184 ) -> str: 3185 return self.connector_sql(expression, "AND", stack) 3186 3187 def or_sql( 3188 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3189 ) -> str: 3190 return self.connector_sql(expression, "OR", stack) 3191 3192 def xor_sql( 3193 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3194 ) -> str: 3195 return self.connector_sql(expression, "XOR", stack) 3196 3197 def connector_sql( 3198 self, 3199 expression: exp.Connector, 3200 op: str, 3201 stack: t.Optional[t.List[str | exp.Expression]] = None, 3202 ) -> str: 3203 if stack is not None: 3204 if expression.expressions: 3205 stack.append(self.expressions(expression, sep=f" {op} ")) 3206 else: 3207 stack.append(expression.right) 3208 if expression.comments and self.comments: 3209 for comment in expression.comments: 3210 if comment: 3211 op += f" /*{self.pad_comment(comment)}*/" 3212 stack.extend((op, expression.left)) 3213 return op 3214 3215 stack = [expression] 3216 sqls: t.List[str] = [] 3217 ops = set() 3218 3219 while stack: 3220 node = stack.pop() 3221 if isinstance(node, exp.Connector): 3222 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3223 else: 3224 sql = self.sql(node) 3225 if sqls and sqls[-1] in ops: 3226 sqls[-1] += f" {sql}" 3227 else: 3228 sqls.append(sql) 3229 3230 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3231 return sep.join(sqls) 3232 3233 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3234 return self.binary(expression, "&") 3235 3236 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3237 return self.binary(expression, "<<") 3238 3239 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3240 return f"~{self.sql(expression, 'this')}" 3241 3242 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3243 return self.binary(expression, "|") 3244 3245 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3246 return self.binary(expression, ">>") 3247 3248 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3249 return self.binary(expression, "^") 3250 3251 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3252 format_sql = self.sql(expression, "format") 3253 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3254 to_sql = self.sql(expression, "to") 3255 to_sql = f" {to_sql}" if to_sql else "" 3256 action = self.sql(expression, "action") 3257 action = f" {action}" if action else "" 3258 default = self.sql(expression, "default") 3259 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3260 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3261 3262 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3263 zone = self.sql(expression, "this") 3264 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3265 3266 def collate_sql(self, expression: exp.Collate) -> str: 3267 if self.COLLATE_IS_FUNC: 3268 return self.function_fallback_sql(expression) 3269 return self.binary(expression, "COLLATE") 3270 3271 def command_sql(self, expression: exp.Command) -> str: 3272 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3273 3274 def comment_sql(self, expression: exp.Comment) -> str: 3275 this = self.sql(expression, "this") 3276 kind = expression.args["kind"] 3277 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3278 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3279 expression_sql = self.sql(expression, "expression") 3280 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3281 3282 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3283 this = self.sql(expression, "this") 3284 delete = " DELETE" if expression.args.get("delete") else "" 3285 recompress = self.sql(expression, "recompress") 3286 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3287 to_disk = self.sql(expression, "to_disk") 3288 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3289 to_volume = self.sql(expression, "to_volume") 3290 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3291 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3292 3293 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3294 where = self.sql(expression, "where") 3295 group = self.sql(expression, "group") 3296 aggregates = self.expressions(expression, key="aggregates") 3297 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3298 3299 if not (where or group or aggregates) and len(expression.expressions) == 1: 3300 return f"TTL {self.expressions(expression, flat=True)}" 3301 3302 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3303 3304 def transaction_sql(self, expression: exp.Transaction) -> str: 3305 return "BEGIN" 3306 3307 def commit_sql(self, expression: exp.Commit) -> str: 3308 chain = expression.args.get("chain") 3309 if chain is not None: 3310 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3311 3312 return f"COMMIT{chain or ''}" 3313 3314 def rollback_sql(self, expression: exp.Rollback) -> str: 3315 savepoint = expression.args.get("savepoint") 3316 savepoint = f" TO {savepoint}" if savepoint else "" 3317 return f"ROLLBACK{savepoint}" 3318 3319 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3320 this = self.sql(expression, "this") 3321 3322 dtype = self.sql(expression, "dtype") 3323 if dtype: 3324 collate = self.sql(expression, "collate") 3325 collate = f" COLLATE {collate}" if collate else "" 3326 using = self.sql(expression, "using") 3327 using = f" USING {using}" if using else "" 3328 return f"ALTER COLUMN {this} {self.ALTER_SET_TYPE} {dtype}{collate}{using}" 3329 3330 default = self.sql(expression, "default") 3331 if default: 3332 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3333 3334 comment = self.sql(expression, "comment") 3335 if comment: 3336 return f"ALTER COLUMN {this} COMMENT {comment}" 3337 3338 visible = expression.args.get("visible") 3339 if visible: 3340 return f"ALTER COLUMN {this} SET {visible}" 3341 3342 allow_null = expression.args.get("allow_null") 3343 drop = expression.args.get("drop") 3344 3345 if not drop and not allow_null: 3346 self.unsupported("Unsupported ALTER COLUMN syntax") 3347 3348 if allow_null is not None: 3349 keyword = "DROP" if drop else "SET" 3350 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3351 3352 return f"ALTER COLUMN {this} DROP DEFAULT" 3353 3354 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3355 this = self.sql(expression, "this") 3356 3357 visible = expression.args.get("visible") 3358 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3359 3360 return f"ALTER INDEX {this} {visible_sql}" 3361 3362 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3363 this = self.sql(expression, "this") 3364 if not isinstance(expression.this, exp.Var): 3365 this = f"KEY DISTKEY {this}" 3366 return f"ALTER DISTSTYLE {this}" 3367 3368 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3369 compound = " COMPOUND" if expression.args.get("compound") else "" 3370 this = self.sql(expression, "this") 3371 expressions = self.expressions(expression, flat=True) 3372 expressions = f"({expressions})" if expressions else "" 3373 return f"ALTER{compound} SORTKEY {this or expressions}" 3374 3375 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3376 if not self.RENAME_TABLE_WITH_DB: 3377 # Remove db from tables 3378 expression = expression.transform( 3379 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3380 ).assert_is(exp.AlterRename) 3381 this = self.sql(expression, "this") 3382 return f"RENAME TO {this}" 3383 3384 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3385 exists = " IF EXISTS" if expression.args.get("exists") else "" 3386 old_column = self.sql(expression, "this") 3387 new_column = self.sql(expression, "to") 3388 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3389 3390 def alterset_sql(self, expression: exp.AlterSet) -> str: 3391 exprs = self.expressions(expression, flat=True) 3392 return f"SET {exprs}" 3393 3394 def alter_sql(self, expression: exp.Alter) -> str: 3395 actions = expression.args["actions"] 3396 3397 if isinstance(actions[0], exp.ColumnDef): 3398 actions = self.add_column_sql(expression) 3399 elif isinstance(actions[0], exp.Schema): 3400 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3401 elif isinstance(actions[0], exp.Delete): 3402 actions = self.expressions(expression, key="actions", flat=True) 3403 elif isinstance(actions[0], exp.Query): 3404 actions = "AS " + self.expressions(expression, key="actions") 3405 else: 3406 actions = self.expressions(expression, key="actions", flat=True) 3407 3408 exists = " IF EXISTS" if expression.args.get("exists") else "" 3409 on_cluster = self.sql(expression, "cluster") 3410 on_cluster = f" {on_cluster}" if on_cluster else "" 3411 only = " ONLY" if expression.args.get("only") else "" 3412 options = self.expressions(expression, key="options") 3413 options = f", {options}" if options else "" 3414 kind = self.sql(expression, "kind") 3415 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3416 3417 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3418 3419 def add_column_sql(self, expression: exp.Alter) -> str: 3420 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3421 return self.expressions( 3422 expression, 3423 key="actions", 3424 prefix="ADD COLUMN ", 3425 skip_first=True, 3426 ) 3427 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3428 3429 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3430 expressions = self.expressions(expression) 3431 exists = " IF EXISTS " if expression.args.get("exists") else " " 3432 return f"DROP{exists}{expressions}" 3433 3434 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3435 return f"ADD {self.expressions(expression)}" 3436 3437 def distinct_sql(self, expression: exp.Distinct) -> str: 3438 this = self.expressions(expression, flat=True) 3439 3440 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3441 case = exp.case() 3442 for arg in expression.expressions: 3443 case = case.when(arg.is_(exp.null()), exp.null()) 3444 this = self.sql(case.else_(f"({this})")) 3445 3446 this = f" {this}" if this else "" 3447 3448 on = self.sql(expression, "on") 3449 on = f" ON {on}" if on else "" 3450 return f"DISTINCT{this}{on}" 3451 3452 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3453 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3454 3455 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3456 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3457 3458 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3459 this_sql = self.sql(expression, "this") 3460 expression_sql = self.sql(expression, "expression") 3461 kind = "MAX" if expression.args.get("max") else "MIN" 3462 return f"{this_sql} HAVING {kind} {expression_sql}" 3463 3464 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3465 return self.sql( 3466 exp.Cast( 3467 this=exp.Div(this=expression.this, expression=expression.expression), 3468 to=exp.DataType(this=exp.DataType.Type.INT), 3469 ) 3470 ) 3471 3472 def dpipe_sql(self, expression: exp.DPipe) -> str: 3473 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3474 return self.func( 3475 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3476 ) 3477 return self.binary(expression, "||") 3478 3479 def div_sql(self, expression: exp.Div) -> str: 3480 l, r = expression.left, expression.right 3481 3482 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3483 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3484 3485 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3486 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3487 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3488 3489 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3490 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3491 return self.sql( 3492 exp.cast( 3493 l / r, 3494 to=exp.DataType.Type.BIGINT, 3495 ) 3496 ) 3497 3498 return self.binary(expression, "/") 3499 3500 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3501 n = exp._wrap(expression.this, exp.Binary) 3502 d = exp._wrap(expression.expression, exp.Binary) 3503 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3504 3505 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3506 return self.binary(expression, "OVERLAPS") 3507 3508 def distance_sql(self, expression: exp.Distance) -> str: 3509 return self.binary(expression, "<->") 3510 3511 def dot_sql(self, expression: exp.Dot) -> str: 3512 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3513 3514 def eq_sql(self, expression: exp.EQ) -> str: 3515 return self.binary(expression, "=") 3516 3517 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3518 return self.binary(expression, ":=") 3519 3520 def escape_sql(self, expression: exp.Escape) -> str: 3521 return self.binary(expression, "ESCAPE") 3522 3523 def glob_sql(self, expression: exp.Glob) -> str: 3524 return self.binary(expression, "GLOB") 3525 3526 def gt_sql(self, expression: exp.GT) -> str: 3527 return self.binary(expression, ">") 3528 3529 def gte_sql(self, expression: exp.GTE) -> str: 3530 return self.binary(expression, ">=") 3531 3532 def ilike_sql(self, expression: exp.ILike) -> str: 3533 return self.binary(expression, "ILIKE") 3534 3535 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3536 return self.binary(expression, "ILIKE ANY") 3537 3538 def is_sql(self, expression: exp.Is) -> str: 3539 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3540 return self.sql( 3541 expression.this if expression.expression.this else exp.not_(expression.this) 3542 ) 3543 return self.binary(expression, "IS") 3544 3545 def like_sql(self, expression: exp.Like) -> str: 3546 return self.binary(expression, "LIKE") 3547 3548 def likeany_sql(self, expression: exp.LikeAny) -> str: 3549 return self.binary(expression, "LIKE ANY") 3550 3551 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3552 return self.binary(expression, "SIMILAR TO") 3553 3554 def lt_sql(self, expression: exp.LT) -> str: 3555 return self.binary(expression, "<") 3556 3557 def lte_sql(self, expression: exp.LTE) -> str: 3558 return self.binary(expression, "<=") 3559 3560 def mod_sql(self, expression: exp.Mod) -> str: 3561 return self.binary(expression, "%") 3562 3563 def mul_sql(self, expression: exp.Mul) -> str: 3564 return self.binary(expression, "*") 3565 3566 def neq_sql(self, expression: exp.NEQ) -> str: 3567 return self.binary(expression, "<>") 3568 3569 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3570 return self.binary(expression, "IS NOT DISTINCT FROM") 3571 3572 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3573 return self.binary(expression, "IS DISTINCT FROM") 3574 3575 def slice_sql(self, expression: exp.Slice) -> str: 3576 return self.binary(expression, ":") 3577 3578 def sub_sql(self, expression: exp.Sub) -> str: 3579 return self.binary(expression, "-") 3580 3581 def trycast_sql(self, expression: exp.TryCast) -> str: 3582 return self.cast_sql(expression, safe_prefix="TRY_") 3583 3584 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3585 return self.cast_sql(expression) 3586 3587 def try_sql(self, expression: exp.Try) -> str: 3588 if not self.TRY_SUPPORTED: 3589 self.unsupported("Unsupported TRY function") 3590 return self.sql(expression, "this") 3591 3592 return self.func("TRY", expression.this) 3593 3594 def log_sql(self, expression: exp.Log) -> str: 3595 this = expression.this 3596 expr = expression.expression 3597 3598 if self.dialect.LOG_BASE_FIRST is False: 3599 this, expr = expr, this 3600 elif self.dialect.LOG_BASE_FIRST is None and expr: 3601 if this.name in ("2", "10"): 3602 return self.func(f"LOG{this.name}", expr) 3603 3604 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3605 3606 return self.func("LOG", this, expr) 3607 3608 def use_sql(self, expression: exp.Use) -> str: 3609 kind = self.sql(expression, "kind") 3610 kind = f" {kind}" if kind else "" 3611 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3612 this = f" {this}" if this else "" 3613 return f"USE{kind}{this}" 3614 3615 def binary(self, expression: exp.Binary, op: str) -> str: 3616 sqls: t.List[str] = [] 3617 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3618 binary_type = type(expression) 3619 3620 while stack: 3621 node = stack.pop() 3622 3623 if type(node) is binary_type: 3624 op_func = node.args.get("operator") 3625 if op_func: 3626 op = f"OPERATOR({self.sql(op_func)})" 3627 3628 stack.append(node.right) 3629 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3630 stack.append(node.left) 3631 else: 3632 sqls.append(self.sql(node)) 3633 3634 return "".join(sqls) 3635 3636 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3637 to_clause = self.sql(expression, "to") 3638 if to_clause: 3639 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3640 3641 return self.function_fallback_sql(expression) 3642 3643 def function_fallback_sql(self, expression: exp.Func) -> str: 3644 args = [] 3645 3646 for key in expression.arg_types: 3647 arg_value = expression.args.get(key) 3648 3649 if isinstance(arg_value, list): 3650 for value in arg_value: 3651 args.append(value) 3652 elif arg_value is not None: 3653 args.append(arg_value) 3654 3655 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3656 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3657 else: 3658 name = expression.sql_name() 3659 3660 return self.func(name, *args) 3661 3662 def func( 3663 self, 3664 name: str, 3665 *args: t.Optional[exp.Expression | str], 3666 prefix: str = "(", 3667 suffix: str = ")", 3668 normalize: bool = True, 3669 ) -> str: 3670 name = self.normalize_func(name) if normalize else name 3671 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3672 3673 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3674 arg_sqls = tuple( 3675 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3676 ) 3677 if self.pretty and self.too_wide(arg_sqls): 3678 return self.indent( 3679 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3680 ) 3681 return sep.join(arg_sqls) 3682 3683 def too_wide(self, args: t.Iterable) -> bool: 3684 return sum(len(arg) for arg in args) > self.max_text_width 3685 3686 def format_time( 3687 self, 3688 expression: exp.Expression, 3689 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3690 inverse_time_trie: t.Optional[t.Dict] = None, 3691 ) -> t.Optional[str]: 3692 return format_time( 3693 self.sql(expression, "format"), 3694 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3695 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3696 ) 3697 3698 def expressions( 3699 self, 3700 expression: t.Optional[exp.Expression] = None, 3701 key: t.Optional[str] = None, 3702 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3703 flat: bool = False, 3704 indent: bool = True, 3705 skip_first: bool = False, 3706 skip_last: bool = False, 3707 sep: str = ", ", 3708 prefix: str = "", 3709 dynamic: bool = False, 3710 new_line: bool = False, 3711 ) -> str: 3712 expressions = expression.args.get(key or "expressions") if expression else sqls 3713 3714 if not expressions: 3715 return "" 3716 3717 if flat: 3718 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3719 3720 num_sqls = len(expressions) 3721 result_sqls = [] 3722 3723 for i, e in enumerate(expressions): 3724 sql = self.sql(e, comment=False) 3725 if not sql: 3726 continue 3727 3728 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3729 3730 if self.pretty: 3731 if self.leading_comma: 3732 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3733 else: 3734 result_sqls.append( 3735 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3736 ) 3737 else: 3738 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3739 3740 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3741 if new_line: 3742 result_sqls.insert(0, "") 3743 result_sqls.append("") 3744 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3745 else: 3746 result_sql = "".join(result_sqls) 3747 3748 return ( 3749 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3750 if indent 3751 else result_sql 3752 ) 3753 3754 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3755 flat = flat or isinstance(expression.parent, exp.Properties) 3756 expressions_sql = self.expressions(expression, flat=flat) 3757 if flat: 3758 return f"{op} {expressions_sql}" 3759 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3760 3761 def naked_property(self, expression: exp.Property) -> str: 3762 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3763 if not property_name: 3764 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3765 return f"{property_name} {self.sql(expression, 'this')}" 3766 3767 def tag_sql(self, expression: exp.Tag) -> str: 3768 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3769 3770 def token_sql(self, token_type: TokenType) -> str: 3771 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3772 3773 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3774 this = self.sql(expression, "this") 3775 expressions = self.no_identify(self.expressions, expression) 3776 expressions = ( 3777 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3778 ) 3779 return f"{this}{expressions}" if expressions.strip() != "" else this 3780 3781 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3782 this = self.sql(expression, "this") 3783 expressions = self.expressions(expression, flat=True) 3784 return f"{this}({expressions})" 3785 3786 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3787 return self.binary(expression, "=>") 3788 3789 def when_sql(self, expression: exp.When) -> str: 3790 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3791 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3792 condition = self.sql(expression, "condition") 3793 condition = f" AND {condition}" if condition else "" 3794 3795 then_expression = expression.args.get("then") 3796 if isinstance(then_expression, exp.Insert): 3797 this = self.sql(then_expression, "this") 3798 this = f"INSERT {this}" if this else "INSERT" 3799 then = self.sql(then_expression, "expression") 3800 then = f"{this} VALUES {then}" if then else this 3801 elif isinstance(then_expression, exp.Update): 3802 if isinstance(then_expression.args.get("expressions"), exp.Star): 3803 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3804 else: 3805 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3806 else: 3807 then = self.sql(then_expression) 3808 return f"WHEN {matched}{source}{condition} THEN {then}" 3809 3810 def whens_sql(self, expression: exp.Whens) -> str: 3811 return self.expressions(expression, sep=" ", indent=False) 3812 3813 def merge_sql(self, expression: exp.Merge) -> str: 3814 table = expression.this 3815 table_alias = "" 3816 3817 hints = table.args.get("hints") 3818 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3819 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3820 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3821 3822 this = self.sql(table) 3823 using = f"USING {self.sql(expression, 'using')}" 3824 on = f"ON {self.sql(expression, 'on')}" 3825 whens = self.sql(expression, "whens") 3826 3827 returning = self.sql(expression, "returning") 3828 if returning: 3829 whens = f"{whens}{returning}" 3830 3831 sep = self.sep() 3832 3833 return self.prepend_ctes( 3834 expression, 3835 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3836 ) 3837 3838 @unsupported_args("format") 3839 def tochar_sql(self, expression: exp.ToChar) -> str: 3840 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3841 3842 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3843 if not self.SUPPORTS_TO_NUMBER: 3844 self.unsupported("Unsupported TO_NUMBER function") 3845 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3846 3847 fmt = expression.args.get("format") 3848 if not fmt: 3849 self.unsupported("Conversion format is required for TO_NUMBER") 3850 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3851 3852 return self.func("TO_NUMBER", expression.this, fmt) 3853 3854 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3855 this = self.sql(expression, "this") 3856 kind = self.sql(expression, "kind") 3857 settings_sql = self.expressions(expression, key="settings", sep=" ") 3858 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3859 return f"{this}({kind}{args})" 3860 3861 def dictrange_sql(self, expression: exp.DictRange) -> str: 3862 this = self.sql(expression, "this") 3863 max = self.sql(expression, "max") 3864 min = self.sql(expression, "min") 3865 return f"{this}(MIN {min} MAX {max})" 3866 3867 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3868 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3869 3870 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3871 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3872 3873 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3874 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3875 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3876 3877 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3878 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3879 expressions = self.expressions(expression, flat=True) 3880 expressions = f" {self.wrap(expressions)}" if expressions else "" 3881 buckets = self.sql(expression, "buckets") 3882 kind = self.sql(expression, "kind") 3883 buckets = f" BUCKETS {buckets}" if buckets else "" 3884 order = self.sql(expression, "order") 3885 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3886 3887 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3888 return "" 3889 3890 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3891 expressions = self.expressions(expression, key="expressions", flat=True) 3892 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3893 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3894 buckets = self.sql(expression, "buckets") 3895 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3896 3897 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3898 this = self.sql(expression, "this") 3899 having = self.sql(expression, "having") 3900 3901 if having: 3902 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3903 3904 return self.func("ANY_VALUE", this) 3905 3906 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3907 transform = self.func("TRANSFORM", *expression.expressions) 3908 row_format_before = self.sql(expression, "row_format_before") 3909 row_format_before = f" {row_format_before}" if row_format_before else "" 3910 record_writer = self.sql(expression, "record_writer") 3911 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3912 using = f" USING {self.sql(expression, 'command_script')}" 3913 schema = self.sql(expression, "schema") 3914 schema = f" AS {schema}" if schema else "" 3915 row_format_after = self.sql(expression, "row_format_after") 3916 row_format_after = f" {row_format_after}" if row_format_after else "" 3917 record_reader = self.sql(expression, "record_reader") 3918 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3919 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3920 3921 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3922 key_block_size = self.sql(expression, "key_block_size") 3923 if key_block_size: 3924 return f"KEY_BLOCK_SIZE = {key_block_size}" 3925 3926 using = self.sql(expression, "using") 3927 if using: 3928 return f"USING {using}" 3929 3930 parser = self.sql(expression, "parser") 3931 if parser: 3932 return f"WITH PARSER {parser}" 3933 3934 comment = self.sql(expression, "comment") 3935 if comment: 3936 return f"COMMENT {comment}" 3937 3938 visible = expression.args.get("visible") 3939 if visible is not None: 3940 return "VISIBLE" if visible else "INVISIBLE" 3941 3942 engine_attr = self.sql(expression, "engine_attr") 3943 if engine_attr: 3944 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3945 3946 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3947 if secondary_engine_attr: 3948 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3949 3950 self.unsupported("Unsupported index constraint option.") 3951 return "" 3952 3953 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3954 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3955 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3956 3957 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3958 kind = self.sql(expression, "kind") 3959 kind = f"{kind} INDEX" if kind else "INDEX" 3960 this = self.sql(expression, "this") 3961 this = f" {this}" if this else "" 3962 index_type = self.sql(expression, "index_type") 3963 index_type = f" USING {index_type}" if index_type else "" 3964 expressions = self.expressions(expression, flat=True) 3965 expressions = f" ({expressions})" if expressions else "" 3966 options = self.expressions(expression, key="options", sep=" ") 3967 options = f" {options}" if options else "" 3968 return f"{kind}{this}{index_type}{expressions}{options}" 3969 3970 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3971 if self.NVL2_SUPPORTED: 3972 return self.function_fallback_sql(expression) 3973 3974 case = exp.Case().when( 3975 expression.this.is_(exp.null()).not_(copy=False), 3976 expression.args["true"], 3977 copy=False, 3978 ) 3979 else_cond = expression.args.get("false") 3980 if else_cond: 3981 case.else_(else_cond, copy=False) 3982 3983 return self.sql(case) 3984 3985 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3986 this = self.sql(expression, "this") 3987 expr = self.sql(expression, "expression") 3988 iterator = self.sql(expression, "iterator") 3989 condition = self.sql(expression, "condition") 3990 condition = f" IF {condition}" if condition else "" 3991 return f"{this} FOR {expr} IN {iterator}{condition}" 3992 3993 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3994 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3995 3996 def opclass_sql(self, expression: exp.Opclass) -> str: 3997 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3998 3999 def predict_sql(self, expression: exp.Predict) -> str: 4000 model = self.sql(expression, "this") 4001 model = f"MODEL {model}" 4002 table = self.sql(expression, "expression") 4003 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4004 parameters = self.sql(expression, "params_struct") 4005 return self.func("PREDICT", model, table, parameters or None) 4006 4007 def forin_sql(self, expression: exp.ForIn) -> str: 4008 this = self.sql(expression, "this") 4009 expression_sql = self.sql(expression, "expression") 4010 return f"FOR {this} DO {expression_sql}" 4011 4012 def refresh_sql(self, expression: exp.Refresh) -> str: 4013 this = self.sql(expression, "this") 4014 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4015 return f"REFRESH {table}{this}" 4016 4017 def toarray_sql(self, expression: exp.ToArray) -> str: 4018 arg = expression.this 4019 if not arg.type: 4020 from sqlglot.optimizer.annotate_types import annotate_types 4021 4022 arg = annotate_types(arg, dialect=self.dialect) 4023 4024 if arg.is_type(exp.DataType.Type.ARRAY): 4025 return self.sql(arg) 4026 4027 cond_for_null = arg.is_(exp.null()) 4028 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4029 4030 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4031 this = expression.this 4032 time_format = self.format_time(expression) 4033 4034 if time_format: 4035 return self.sql( 4036 exp.cast( 4037 exp.StrToTime(this=this, format=expression.args["format"]), 4038 exp.DataType.Type.TIME, 4039 ) 4040 ) 4041 4042 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4043 return self.sql(this) 4044 4045 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4046 4047 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4048 this = expression.this 4049 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4050 return self.sql(this) 4051 4052 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4053 4054 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4055 this = expression.this 4056 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4057 return self.sql(this) 4058 4059 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4060 4061 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4062 this = expression.this 4063 time_format = self.format_time(expression) 4064 4065 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4066 return self.sql( 4067 exp.cast( 4068 exp.StrToTime(this=this, format=expression.args["format"]), 4069 exp.DataType.Type.DATE, 4070 ) 4071 ) 4072 4073 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4074 return self.sql(this) 4075 4076 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4077 4078 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4079 return self.sql( 4080 exp.func( 4081 "DATEDIFF", 4082 expression.this, 4083 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4084 "day", 4085 ) 4086 ) 4087 4088 def lastday_sql(self, expression: exp.LastDay) -> str: 4089 if self.LAST_DAY_SUPPORTS_DATE_PART: 4090 return self.function_fallback_sql(expression) 4091 4092 unit = expression.text("unit") 4093 if unit and unit != "MONTH": 4094 self.unsupported("Date parts are not supported in LAST_DAY.") 4095 4096 return self.func("LAST_DAY", expression.this) 4097 4098 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4099 from sqlglot.dialects.dialect import unit_to_str 4100 4101 return self.func( 4102 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4103 ) 4104 4105 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4106 if self.CAN_IMPLEMENT_ARRAY_ANY: 4107 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4108 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4109 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4110 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4111 4112 from sqlglot.dialects import Dialect 4113 4114 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4115 if self.dialect.__class__ != Dialect: 4116 self.unsupported("ARRAY_ANY is unsupported") 4117 4118 return self.function_fallback_sql(expression) 4119 4120 def struct_sql(self, expression: exp.Struct) -> str: 4121 expression.set( 4122 "expressions", 4123 [ 4124 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4125 if isinstance(e, exp.PropertyEQ) 4126 else e 4127 for e in expression.expressions 4128 ], 4129 ) 4130 4131 return self.function_fallback_sql(expression) 4132 4133 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4134 low = self.sql(expression, "this") 4135 high = self.sql(expression, "expression") 4136 4137 return f"{low} TO {high}" 4138 4139 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4140 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4141 tables = f" {self.expressions(expression)}" 4142 4143 exists = " IF EXISTS" if expression.args.get("exists") else "" 4144 4145 on_cluster = self.sql(expression, "cluster") 4146 on_cluster = f" {on_cluster}" if on_cluster else "" 4147 4148 identity = self.sql(expression, "identity") 4149 identity = f" {identity} IDENTITY" if identity else "" 4150 4151 option = self.sql(expression, "option") 4152 option = f" {option}" if option else "" 4153 4154 partition = self.sql(expression, "partition") 4155 partition = f" {partition}" if partition else "" 4156 4157 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4158 4159 # This transpiles T-SQL's CONVERT function 4160 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4161 def convert_sql(self, expression: exp.Convert) -> str: 4162 to = expression.this 4163 value = expression.expression 4164 style = expression.args.get("style") 4165 safe = expression.args.get("safe") 4166 strict = expression.args.get("strict") 4167 4168 if not to or not value: 4169 return "" 4170 4171 # Retrieve length of datatype and override to default if not specified 4172 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4173 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4174 4175 transformed: t.Optional[exp.Expression] = None 4176 cast = exp.Cast if strict else exp.TryCast 4177 4178 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4179 if isinstance(style, exp.Literal) and style.is_int: 4180 from sqlglot.dialects.tsql import TSQL 4181 4182 style_value = style.name 4183 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4184 if not converted_style: 4185 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4186 4187 fmt = exp.Literal.string(converted_style) 4188 4189 if to.this == exp.DataType.Type.DATE: 4190 transformed = exp.StrToDate(this=value, format=fmt) 4191 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4192 transformed = exp.StrToTime(this=value, format=fmt) 4193 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4194 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4195 elif to.this == exp.DataType.Type.TEXT: 4196 transformed = exp.TimeToStr(this=value, format=fmt) 4197 4198 if not transformed: 4199 transformed = cast(this=value, to=to, safe=safe) 4200 4201 return self.sql(transformed) 4202 4203 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4204 this = expression.this 4205 if isinstance(this, exp.JSONPathWildcard): 4206 this = self.json_path_part(this) 4207 return f".{this}" if this else "" 4208 4209 if exp.SAFE_IDENTIFIER_RE.match(this): 4210 return f".{this}" 4211 4212 this = self.json_path_part(this) 4213 return ( 4214 f"[{this}]" 4215 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4216 else f".{this}" 4217 ) 4218 4219 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4220 this = self.json_path_part(expression.this) 4221 return f"[{this}]" if this else "" 4222 4223 def _simplify_unless_literal(self, expression: E) -> E: 4224 if not isinstance(expression, exp.Literal): 4225 from sqlglot.optimizer.simplify import simplify 4226 4227 expression = simplify(expression, dialect=self.dialect) 4228 4229 return expression 4230 4231 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4232 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4233 # The first modifier here will be the one closest to the AggFunc's arg 4234 mods = sorted( 4235 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4236 key=lambda x: 0 4237 if isinstance(x, exp.HavingMax) 4238 else (1 if isinstance(x, exp.Order) else 2), 4239 ) 4240 4241 if mods: 4242 mod = mods[0] 4243 this = expression.__class__(this=mod.this.copy()) 4244 this.meta["inline"] = True 4245 mod.this.replace(this) 4246 return self.sql(expression.this) 4247 4248 agg_func = expression.find(exp.AggFunc) 4249 4250 if agg_func: 4251 return self.sql(agg_func)[:-1] + f" {text})" 4252 4253 return f"{self.sql(expression, 'this')} {text}" 4254 4255 def _replace_line_breaks(self, string: str) -> str: 4256 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4257 if self.pretty: 4258 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4259 return string 4260 4261 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4262 option = self.sql(expression, "this") 4263 4264 if expression.expressions: 4265 upper = option.upper() 4266 4267 # Snowflake FILE_FORMAT options are separated by whitespace 4268 sep = " " if upper == "FILE_FORMAT" else ", " 4269 4270 # Databricks copy/format options do not set their list of values with EQ 4271 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4272 values = self.expressions(expression, flat=True, sep=sep) 4273 return f"{option}{op}({values})" 4274 4275 value = self.sql(expression, "expression") 4276 4277 if not value: 4278 return option 4279 4280 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4281 4282 return f"{option}{op}{value}" 4283 4284 def credentials_sql(self, expression: exp.Credentials) -> str: 4285 cred_expr = expression.args.get("credentials") 4286 if isinstance(cred_expr, exp.Literal): 4287 # Redshift case: CREDENTIALS <string> 4288 credentials = self.sql(expression, "credentials") 4289 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4290 else: 4291 # Snowflake case: CREDENTIALS = (...) 4292 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4293 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4294 4295 storage = self.sql(expression, "storage") 4296 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4297 4298 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4299 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4300 4301 iam_role = self.sql(expression, "iam_role") 4302 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4303 4304 region = self.sql(expression, "region") 4305 region = f" REGION {region}" if region else "" 4306 4307 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4308 4309 def copy_sql(self, expression: exp.Copy) -> str: 4310 this = self.sql(expression, "this") 4311 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4312 4313 credentials = self.sql(expression, "credentials") 4314 credentials = self.seg(credentials) if credentials else "" 4315 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4316 files = self.expressions(expression, key="files", flat=True) 4317 4318 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4319 params = self.expressions( 4320 expression, 4321 key="params", 4322 sep=sep, 4323 new_line=True, 4324 skip_last=True, 4325 skip_first=True, 4326 indent=self.COPY_PARAMS_ARE_WRAPPED, 4327 ) 4328 4329 if params: 4330 if self.COPY_PARAMS_ARE_WRAPPED: 4331 params = f" WITH ({params})" 4332 elif not self.pretty: 4333 params = f" {params}" 4334 4335 return f"COPY{this}{kind} {files}{credentials}{params}" 4336 4337 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4338 return "" 4339 4340 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4341 on_sql = "ON" if expression.args.get("on") else "OFF" 4342 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4343 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4344 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4345 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4346 4347 if filter_col or retention_period: 4348 on_sql = self.func("ON", filter_col, retention_period) 4349 4350 return f"DATA_DELETION={on_sql}" 4351 4352 def maskingpolicycolumnconstraint_sql( 4353 self, expression: exp.MaskingPolicyColumnConstraint 4354 ) -> str: 4355 this = self.sql(expression, "this") 4356 expressions = self.expressions(expression, flat=True) 4357 expressions = f" USING ({expressions})" if expressions else "" 4358 return f"MASKING POLICY {this}{expressions}" 4359 4360 def gapfill_sql(self, expression: exp.GapFill) -> str: 4361 this = self.sql(expression, "this") 4362 this = f"TABLE {this}" 4363 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4364 4365 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4366 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4367 4368 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4369 this = self.sql(expression, "this") 4370 expr = expression.expression 4371 4372 if isinstance(expr, exp.Func): 4373 # T-SQL's CLR functions are case sensitive 4374 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4375 else: 4376 expr = self.sql(expression, "expression") 4377 4378 return self.scope_resolution(expr, this) 4379 4380 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4381 if self.PARSE_JSON_NAME is None: 4382 return self.sql(expression.this) 4383 4384 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4385 4386 def rand_sql(self, expression: exp.Rand) -> str: 4387 lower = self.sql(expression, "lower") 4388 upper = self.sql(expression, "upper") 4389 4390 if lower and upper: 4391 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4392 return self.func("RAND", expression.this) 4393 4394 def changes_sql(self, expression: exp.Changes) -> str: 4395 information = self.sql(expression, "information") 4396 information = f"INFORMATION => {information}" 4397 at_before = self.sql(expression, "at_before") 4398 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4399 end = self.sql(expression, "end") 4400 end = f"{self.seg('')}{end}" if end else "" 4401 4402 return f"CHANGES ({information}){at_before}{end}" 4403 4404 def pad_sql(self, expression: exp.Pad) -> str: 4405 prefix = "L" if expression.args.get("is_left") else "R" 4406 4407 fill_pattern = self.sql(expression, "fill_pattern") or None 4408 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4409 fill_pattern = "' '" 4410 4411 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4412 4413 def summarize_sql(self, expression: exp.Summarize) -> str: 4414 table = " TABLE" if expression.args.get("table") else "" 4415 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4416 4417 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4418 generate_series = exp.GenerateSeries(**expression.args) 4419 4420 parent = expression.parent 4421 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4422 parent = parent.parent 4423 4424 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4425 return self.sql(exp.Unnest(expressions=[generate_series])) 4426 4427 if isinstance(parent, exp.Select): 4428 self.unsupported("GenerateSeries projection unnesting is not supported.") 4429 4430 return self.sql(generate_series) 4431 4432 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4433 exprs = expression.expressions 4434 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4435 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4436 else: 4437 rhs = self.expressions(expression) 4438 4439 return self.func(name, expression.this, rhs or None) 4440 4441 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4442 if self.SUPPORTS_CONVERT_TIMEZONE: 4443 return self.function_fallback_sql(expression) 4444 4445 source_tz = expression.args.get("source_tz") 4446 target_tz = expression.args.get("target_tz") 4447 timestamp = expression.args.get("timestamp") 4448 4449 if source_tz and timestamp: 4450 timestamp = exp.AtTimeZone( 4451 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4452 ) 4453 4454 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4455 4456 return self.sql(expr) 4457 4458 def json_sql(self, expression: exp.JSON) -> str: 4459 this = self.sql(expression, "this") 4460 this = f" {this}" if this else "" 4461 4462 _with = expression.args.get("with") 4463 4464 if _with is None: 4465 with_sql = "" 4466 elif not _with: 4467 with_sql = " WITHOUT" 4468 else: 4469 with_sql = " WITH" 4470 4471 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4472 4473 return f"JSON{this}{with_sql}{unique_sql}" 4474 4475 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4476 def _generate_on_options(arg: t.Any) -> str: 4477 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4478 4479 path = self.sql(expression, "path") 4480 returning = self.sql(expression, "returning") 4481 returning = f" RETURNING {returning}" if returning else "" 4482 4483 on_condition = self.sql(expression, "on_condition") 4484 on_condition = f" {on_condition}" if on_condition else "" 4485 4486 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4487 4488 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4489 else_ = "ELSE " if expression.args.get("else_") else "" 4490 condition = self.sql(expression, "expression") 4491 condition = f"WHEN {condition} THEN " if condition else else_ 4492 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4493 return f"{condition}{insert}" 4494 4495 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4496 kind = self.sql(expression, "kind") 4497 expressions = self.seg(self.expressions(expression, sep=" ")) 4498 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4499 return res 4500 4501 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4502 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4503 empty = expression.args.get("empty") 4504 empty = ( 4505 f"DEFAULT {empty} ON EMPTY" 4506 if isinstance(empty, exp.Expression) 4507 else self.sql(expression, "empty") 4508 ) 4509 4510 error = expression.args.get("error") 4511 error = ( 4512 f"DEFAULT {error} ON ERROR" 4513 if isinstance(error, exp.Expression) 4514 else self.sql(expression, "error") 4515 ) 4516 4517 if error and empty: 4518 error = ( 4519 f"{empty} {error}" 4520 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4521 else f"{error} {empty}" 4522 ) 4523 empty = "" 4524 4525 null = self.sql(expression, "null") 4526 4527 return f"{empty}{error}{null}" 4528 4529 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4530 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4531 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4532 4533 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4534 this = self.sql(expression, "this") 4535 path = self.sql(expression, "path") 4536 4537 passing = self.expressions(expression, "passing") 4538 passing = f" PASSING {passing}" if passing else "" 4539 4540 on_condition = self.sql(expression, "on_condition") 4541 on_condition = f" {on_condition}" if on_condition else "" 4542 4543 path = f"{path}{passing}{on_condition}" 4544 4545 return self.func("JSON_EXISTS", this, path) 4546 4547 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4548 array_agg = self.function_fallback_sql(expression) 4549 4550 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4551 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4552 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4553 parent = expression.parent 4554 if isinstance(parent, exp.Filter): 4555 parent_cond = parent.expression.this 4556 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4557 else: 4558 this = expression.this 4559 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4560 if this.find(exp.Column): 4561 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4562 this_sql = ( 4563 self.expressions(this) 4564 if isinstance(this, exp.Distinct) 4565 else self.sql(expression, "this") 4566 ) 4567 4568 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4569 4570 return array_agg 4571 4572 def apply_sql(self, expression: exp.Apply) -> str: 4573 this = self.sql(expression, "this") 4574 expr = self.sql(expression, "expression") 4575 4576 return f"{this} APPLY({expr})" 4577 4578 def grant_sql(self, expression: exp.Grant) -> str: 4579 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4580 4581 kind = self.sql(expression, "kind") 4582 kind = f" {kind}" if kind else "" 4583 4584 securable = self.sql(expression, "securable") 4585 securable = f" {securable}" if securable else "" 4586 4587 principals = self.expressions(expression, key="principals", flat=True) 4588 4589 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4590 4591 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4592 4593 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4594 this = self.sql(expression, "this") 4595 columns = self.expressions(expression, flat=True) 4596 columns = f"({columns})" if columns else "" 4597 4598 return f"{this}{columns}" 4599 4600 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4601 this = self.sql(expression, "this") 4602 4603 kind = self.sql(expression, "kind") 4604 kind = f"{kind} " if kind else "" 4605 4606 return f"{kind}{this}" 4607 4608 def columns_sql(self, expression: exp.Columns): 4609 func = self.function_fallback_sql(expression) 4610 if expression.args.get("unpack"): 4611 func = f"*{func}" 4612 4613 return func 4614 4615 def overlay_sql(self, expression: exp.Overlay): 4616 this = self.sql(expression, "this") 4617 expr = self.sql(expression, "expression") 4618 from_sql = self.sql(expression, "from") 4619 for_sql = self.sql(expression, "for") 4620 for_sql = f" FOR {for_sql}" if for_sql else "" 4621 4622 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4623 4624 @unsupported_args("format") 4625 def todouble_sql(self, expression: exp.ToDouble) -> str: 4626 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4627 4628 def string_sql(self, expression: exp.String) -> str: 4629 this = expression.this 4630 zone = expression.args.get("zone") 4631 4632 if zone: 4633 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4634 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4635 # set for source_tz to transpile the time conversion before the STRING cast 4636 this = exp.ConvertTimezone( 4637 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4638 ) 4639 4640 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4641 4642 def median_sql(self, expression: exp.Median): 4643 if not self.SUPPORTS_MEDIAN: 4644 return self.sql( 4645 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4646 ) 4647 4648 return self.function_fallback_sql(expression) 4649 4650 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4651 filler = self.sql(expression, "this") 4652 filler = f" {filler}" if filler else "" 4653 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4654 return f"TRUNCATE{filler} {with_count}" 4655 4656 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4657 if self.SUPPORTS_UNIX_SECONDS: 4658 return self.function_fallback_sql(expression) 4659 4660 start_ts = exp.cast( 4661 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4662 ) 4663 4664 return self.sql( 4665 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4666 ) 4667 4668 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4669 dim = expression.expression 4670 4671 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4672 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4673 if not (dim.is_int and dim.name == "1"): 4674 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4675 dim = None 4676 4677 # If dimension is required but not specified, default initialize it 4678 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4679 dim = exp.Literal.number(1) 4680 4681 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4682 4683 def attach_sql(self, expression: exp.Attach) -> str: 4684 this = self.sql(expression, "this") 4685 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4686 expressions = self.expressions(expression) 4687 expressions = f" ({expressions})" if expressions else "" 4688 4689 return f"ATTACH{exists_sql} {this}{expressions}" 4690 4691 def detach_sql(self, expression: exp.Detach) -> str: 4692 this = self.sql(expression, "this") 4693 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 4694 4695 return f"DETACH{exists_sql} {this}" 4696 4697 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4698 this = self.sql(expression, "this") 4699 value = self.sql(expression, "expression") 4700 value = f" {value}" if value else "" 4701 return f"{this}{value}" 4702 4703 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4704 this_sql = self.sql(expression, "this") 4705 if isinstance(expression.this, exp.Table): 4706 this_sql = f"TABLE {this_sql}" 4707 4708 return self.func( 4709 "FEATURES_AT_TIME", 4710 this_sql, 4711 expression.args.get("time"), 4712 expression.args.get("num_rows"), 4713 expression.args.get("ignore_feature_nulls"), 4714 ) 4715 4716 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4717 return ( 4718 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4719 ) 4720 4721 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4722 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4723 encode = f"{encode} {self.sql(expression, 'this')}" 4724 4725 properties = expression.args.get("properties") 4726 if properties: 4727 encode = f"{encode} {self.properties(properties)}" 4728 4729 return encode 4730 4731 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4732 this = self.sql(expression, "this") 4733 include = f"INCLUDE {this}" 4734 4735 column_def = self.sql(expression, "column_def") 4736 if column_def: 4737 include = f"{include} {column_def}" 4738 4739 alias = self.sql(expression, "alias") 4740 if alias: 4741 include = f"{include} AS {alias}" 4742 4743 return include 4744 4745 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4746 name = f"NAME {self.sql(expression, 'this')}" 4747 return self.func("XMLELEMENT", name, *expression.expressions) 4748 4749 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4750 partitions = self.expressions(expression, "partition_expressions") 4751 create = self.expressions(expression, "create_expressions") 4752 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4753 4754 def partitionbyrangepropertydynamic_sql( 4755 self, expression: exp.PartitionByRangePropertyDynamic 4756 ) -> str: 4757 start = self.sql(expression, "start") 4758 end = self.sql(expression, "end") 4759 4760 every = expression.args["every"] 4761 if isinstance(every, exp.Interval) and every.this.is_string: 4762 every.this.replace(exp.Literal.number(every.name)) 4763 4764 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4765 4766 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4767 name = self.sql(expression, "this") 4768 values = self.expressions(expression, flat=True) 4769 4770 return f"NAME {name} VALUE {values}" 4771 4772 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4773 kind = self.sql(expression, "kind") 4774 sample = self.sql(expression, "sample") 4775 return f"SAMPLE {sample} {kind}" 4776 4777 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4778 kind = self.sql(expression, "kind") 4779 option = self.sql(expression, "option") 4780 option = f" {option}" if option else "" 4781 this = self.sql(expression, "this") 4782 this = f" {this}" if this else "" 4783 columns = self.expressions(expression) 4784 columns = f" {columns}" if columns else "" 4785 return f"{kind}{option} STATISTICS{this}{columns}" 4786 4787 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4788 this = self.sql(expression, "this") 4789 columns = self.expressions(expression) 4790 inner_expression = self.sql(expression, "expression") 4791 inner_expression = f" {inner_expression}" if inner_expression else "" 4792 update_options = self.sql(expression, "update_options") 4793 update_options = f" {update_options} UPDATE" if update_options else "" 4794 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4795 4796 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4797 kind = self.sql(expression, "kind") 4798 kind = f" {kind}" if kind else "" 4799 return f"DELETE{kind} STATISTICS" 4800 4801 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4802 inner_expression = self.sql(expression, "expression") 4803 return f"LIST CHAINED ROWS{inner_expression}" 4804 4805 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4806 kind = self.sql(expression, "kind") 4807 this = self.sql(expression, "this") 4808 this = f" {this}" if this else "" 4809 inner_expression = self.sql(expression, "expression") 4810 return f"VALIDATE {kind}{this}{inner_expression}" 4811 4812 def analyze_sql(self, expression: exp.Analyze) -> str: 4813 options = self.expressions(expression, key="options", sep=" ") 4814 options = f" {options}" if options else "" 4815 kind = self.sql(expression, "kind") 4816 kind = f" {kind}" if kind else "" 4817 this = self.sql(expression, "this") 4818 this = f" {this}" if this else "" 4819 mode = self.sql(expression, "mode") 4820 mode = f" {mode}" if mode else "" 4821 properties = self.sql(expression, "properties") 4822 properties = f" {properties}" if properties else "" 4823 partition = self.sql(expression, "partition") 4824 partition = f" {partition}" if partition else "" 4825 inner_expression = self.sql(expression, "expression") 4826 inner_expression = f" {inner_expression}" if inner_expression else "" 4827 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4828 4829 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4830 this = self.sql(expression, "this") 4831 namespaces = self.expressions(expression, key="namespaces") 4832 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4833 passing = self.expressions(expression, key="passing") 4834 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4835 columns = self.expressions(expression, key="columns") 4836 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4837 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4838 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4839 4840 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4841 this = self.sql(expression, "this") 4842 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4843 4844 def export_sql(self, expression: exp.Export) -> str: 4845 this = self.sql(expression, "this") 4846 connection = self.sql(expression, "connection") 4847 connection = f"WITH CONNECTION {connection} " if connection else "" 4848 options = self.sql(expression, "options") 4849 return f"EXPORT DATA {connection}{options} AS {this}" 4850 4851 def declare_sql(self, expression: exp.Declare) -> str: 4852 return f"DECLARE {self.expressions(expression, flat=True)}" 4853 4854 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4855 variable = self.sql(expression, "this") 4856 default = self.sql(expression, "default") 4857 default = f" = {default}" if default else "" 4858 4859 kind = self.sql(expression, "kind") 4860 if isinstance(expression.args.get("kind"), exp.Schema): 4861 kind = f"TABLE {kind}" 4862 4863 return f"{variable} AS {kind}{default}" 4864 4865 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4866 kind = self.sql(expression, "kind") 4867 this = self.sql(expression, "this") 4868 set = self.sql(expression, "expression") 4869 using = self.sql(expression, "using") 4870 using = f" USING {using}" if using else "" 4871 4872 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4873 4874 return f"{kind_sql} {this} SET {set}{using}" 4875 4876 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4877 params = self.expressions(expression, key="params", flat=True) 4878 return self.func(expression.name, *expression.expressions) + f"({params})" 4879 4880 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4881 return self.func(expression.name, *expression.expressions) 4882 4883 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4884 return self.anonymousaggfunc_sql(expression) 4885 4886 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 4887 return self.parameterizedagg_sql(expression) 4888 4889 def show_sql(self, expression: exp.Show) -> str: 4890 self.unsupported("Unsupported SHOW statement") 4891 return "" 4892 4893 def put_sql(self, expression: exp.Put) -> str: 4894 props = expression.args.get("properties") 4895 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4896 this = self.sql(expression, "this") 4897 target = self.sql(expression, "target") 4898 return f"PUT {this} {target}{props_sql}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 136 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 137 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 138 exp.DynamicProperty: lambda *_: "DYNAMIC", 139 exp.EmptyProperty: lambda *_: "EMPTY", 140 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 141 exp.EphemeralColumnConstraint: lambda self, 142 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 143 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 144 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 145 exp.Except: lambda self, e: self.set_operations(e), 146 exp.ExternalProperty: lambda *_: "EXTERNAL", 147 exp.Floor: lambda self, e: self.ceil_floor(e), 148 exp.GlobalProperty: lambda *_: "GLOBAL", 149 exp.HeapProperty: lambda *_: "HEAP", 150 exp.IcebergProperty: lambda *_: "ICEBERG", 151 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 152 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 153 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 154 exp.Intersect: lambda self, e: self.set_operations(e), 155 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 156 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 157 exp.LanguageProperty: lambda self, e: self.naked_property(e), 158 exp.LocationProperty: lambda self, e: self.naked_property(e), 159 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 160 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 161 exp.NonClusteredColumnConstraint: lambda self, 162 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 163 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 164 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 165 exp.OnCommitProperty: lambda _, 166 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 167 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 168 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 169 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 170 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 171 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 172 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 173 exp.ProjectionPolicyColumnConstraint: lambda self, 174 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 175 exp.RemoteWithConnectionModelProperty: lambda self, 176 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 177 exp.ReturnsProperty: lambda self, e: ( 178 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 179 ), 180 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 181 exp.SecureProperty: lambda *_: "SECURE", 182 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 183 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 184 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 185 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 186 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 187 exp.SqlReadWriteProperty: lambda _, e: e.name, 188 exp.SqlSecurityProperty: lambda _, 189 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 190 exp.StabilityProperty: lambda _, e: e.name, 191 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 192 exp.StreamingTableProperty: lambda *_: "STREAMING", 193 exp.StrictProperty: lambda *_: "STRICT", 194 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 195 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 196 exp.TemporaryProperty: lambda *_: "TEMPORARY", 197 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 198 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 199 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 200 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 201 exp.TransientProperty: lambda *_: "TRANSIENT", 202 exp.Union: lambda self, e: self.set_operations(e), 203 exp.UnloggedProperty: lambda *_: "UNLOGGED", 204 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 205 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 206 exp.Uuid: lambda *_: "UUID()", 207 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 208 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 209 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 210 exp.VolatileProperty: lambda *_: "VOLATILE", 211 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 212 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 213 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 214 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 215 exp.ForceProperty: lambda *_: "FORCE", 216 } 217 218 # Whether null ordering is supported in order by 219 # True: Full Support, None: No support, False: No support for certain cases 220 # such as window specifications, aggregate functions etc 221 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 222 223 # Whether ignore nulls is inside the agg or outside. 224 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 225 IGNORE_NULLS_IN_FUNC = False 226 227 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 228 LOCKING_READS_SUPPORTED = False 229 230 # Whether the EXCEPT and INTERSECT operations can return duplicates 231 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 232 233 # Wrap derived values in parens, usually standard but spark doesn't support it 234 WRAP_DERIVED_VALUES = True 235 236 # Whether create function uses an AS before the RETURN 237 CREATE_FUNCTION_RETURN_AS = True 238 239 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 240 MATCHED_BY_SOURCE = True 241 242 # Whether the INTERVAL expression works only with values like '1 day' 243 SINGLE_STRING_INTERVAL = False 244 245 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 246 INTERVAL_ALLOWS_PLURAL_FORM = True 247 248 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 249 LIMIT_FETCH = "ALL" 250 251 # Whether limit and fetch allows expresions or just limits 252 LIMIT_ONLY_LITERALS = False 253 254 # Whether a table is allowed to be renamed with a db 255 RENAME_TABLE_WITH_DB = True 256 257 # The separator for grouping sets and rollups 258 GROUPINGS_SEP = "," 259 260 # The string used for creating an index on a table 261 INDEX_ON = "ON" 262 263 # Whether join hints should be generated 264 JOIN_HINTS = True 265 266 # Whether table hints should be generated 267 TABLE_HINTS = True 268 269 # Whether query hints should be generated 270 QUERY_HINTS = True 271 272 # What kind of separator to use for query hints 273 QUERY_HINT_SEP = ", " 274 275 # Whether comparing against booleans (e.g. x IS TRUE) is supported 276 IS_BOOL_ALLOWED = True 277 278 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 279 DUPLICATE_KEY_UPDATE_WITH_SET = True 280 281 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 282 LIMIT_IS_TOP = False 283 284 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 285 RETURNING_END = True 286 287 # Whether to generate an unquoted value for EXTRACT's date part argument 288 EXTRACT_ALLOWS_QUOTES = True 289 290 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 291 TZ_TO_WITH_TIME_ZONE = False 292 293 # Whether the NVL2 function is supported 294 NVL2_SUPPORTED = True 295 296 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 297 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 298 299 # Whether VALUES statements can be used as derived tables. 300 # MySQL 5 and Redshift do not allow this, so when False, it will convert 301 # SELECT * VALUES into SELECT UNION 302 VALUES_AS_TABLE = True 303 304 # Whether the word COLUMN is included when adding a column with ALTER TABLE 305 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 306 307 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 308 UNNEST_WITH_ORDINALITY = True 309 310 # Whether FILTER (WHERE cond) can be used for conditional aggregation 311 AGGREGATE_FILTER_SUPPORTED = True 312 313 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 314 SEMI_ANTI_JOIN_WITH_SIDE = True 315 316 # Whether to include the type of a computed column in the CREATE DDL 317 COMPUTED_COLUMN_WITH_TYPE = True 318 319 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 320 SUPPORTS_TABLE_COPY = True 321 322 # Whether parentheses are required around the table sample's expression 323 TABLESAMPLE_REQUIRES_PARENS = True 324 325 # Whether a table sample clause's size needs to be followed by the ROWS keyword 326 TABLESAMPLE_SIZE_IS_ROWS = True 327 328 # The keyword(s) to use when generating a sample clause 329 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 330 331 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 332 TABLESAMPLE_WITH_METHOD = True 333 334 # The keyword to use when specifying the seed of a sample clause 335 TABLESAMPLE_SEED_KEYWORD = "SEED" 336 337 # Whether COLLATE is a function instead of a binary operator 338 COLLATE_IS_FUNC = False 339 340 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 341 DATA_TYPE_SPECIFIERS_ALLOWED = False 342 343 # Whether conditions require booleans WHERE x = 0 vs WHERE x 344 ENSURE_BOOLS = False 345 346 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 347 CTE_RECURSIVE_KEYWORD_REQUIRED = True 348 349 # Whether CONCAT requires >1 arguments 350 SUPPORTS_SINGLE_ARG_CONCAT = True 351 352 # Whether LAST_DAY function supports a date part argument 353 LAST_DAY_SUPPORTS_DATE_PART = True 354 355 # Whether named columns are allowed in table aliases 356 SUPPORTS_TABLE_ALIAS_COLUMNS = True 357 358 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 359 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 360 361 # What delimiter to use for separating JSON key/value pairs 362 JSON_KEY_VALUE_PAIR_SEP = ":" 363 364 # INSERT OVERWRITE TABLE x override 365 INSERT_OVERWRITE = " OVERWRITE TABLE" 366 367 # Whether the SELECT .. INTO syntax is used instead of CTAS 368 SUPPORTS_SELECT_INTO = False 369 370 # Whether UNLOGGED tables can be created 371 SUPPORTS_UNLOGGED_TABLES = False 372 373 # Whether the CREATE TABLE LIKE statement is supported 374 SUPPORTS_CREATE_TABLE_LIKE = True 375 376 # Whether the LikeProperty needs to be specified inside of the schema clause 377 LIKE_PROPERTY_INSIDE_SCHEMA = False 378 379 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 380 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 381 MULTI_ARG_DISTINCT = True 382 383 # Whether the JSON extraction operators expect a value of type JSON 384 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 385 386 # Whether bracketed keys like ["foo"] are supported in JSON paths 387 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 388 389 # Whether to escape keys using single quotes in JSON paths 390 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 391 392 # The JSONPathPart expressions supported by this dialect 393 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 394 395 # Whether any(f(x) for x in array) can be implemented by this dialect 396 CAN_IMPLEMENT_ARRAY_ANY = False 397 398 # Whether the function TO_NUMBER is supported 399 SUPPORTS_TO_NUMBER = True 400 401 # Whether or not set op modifiers apply to the outer set op or select. 402 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 403 # True means limit 1 happens after the set op, False means it it happens on y. 404 SET_OP_MODIFIERS = True 405 406 # Whether parameters from COPY statement are wrapped in parentheses 407 COPY_PARAMS_ARE_WRAPPED = True 408 409 # Whether values of params are set with "=" token or empty space 410 COPY_PARAMS_EQ_REQUIRED = False 411 412 # Whether COPY statement has INTO keyword 413 COPY_HAS_INTO_KEYWORD = True 414 415 # Whether the conditional TRY(expression) function is supported 416 TRY_SUPPORTED = True 417 418 # Whether the UESCAPE syntax in unicode strings is supported 419 SUPPORTS_UESCAPE = True 420 421 # The keyword to use when generating a star projection with excluded columns 422 STAR_EXCEPT = "EXCEPT" 423 424 # The HEX function name 425 HEX_FUNC = "HEX" 426 427 # The keywords to use when prefixing & separating WITH based properties 428 WITH_PROPERTIES_PREFIX = "WITH" 429 430 # Whether to quote the generated expression of exp.JsonPath 431 QUOTE_JSON_PATH = True 432 433 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 434 PAD_FILL_PATTERN_IS_REQUIRED = False 435 436 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 437 SUPPORTS_EXPLODING_PROJECTIONS = True 438 439 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 440 ARRAY_CONCAT_IS_VAR_LEN = True 441 442 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 443 SUPPORTS_CONVERT_TIMEZONE = False 444 445 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 446 SUPPORTS_MEDIAN = True 447 448 # Whether UNIX_SECONDS(timestamp) is supported 449 SUPPORTS_UNIX_SECONDS = False 450 451 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 452 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 453 454 # The function name of the exp.ArraySize expression 455 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 456 457 # The syntax to use when altering the type of a column 458 ALTER_SET_TYPE = "SET DATA TYPE" 459 460 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 461 # None -> Doesn't support it at all 462 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 463 # True (Postgres) -> Explicitly requires it 464 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 465 466 TYPE_MAPPING = { 467 exp.DataType.Type.DATETIME2: "TIMESTAMP", 468 exp.DataType.Type.NCHAR: "CHAR", 469 exp.DataType.Type.NVARCHAR: "VARCHAR", 470 exp.DataType.Type.MEDIUMTEXT: "TEXT", 471 exp.DataType.Type.LONGTEXT: "TEXT", 472 exp.DataType.Type.TINYTEXT: "TEXT", 473 exp.DataType.Type.BLOB: "VARBINARY", 474 exp.DataType.Type.MEDIUMBLOB: "BLOB", 475 exp.DataType.Type.LONGBLOB: "BLOB", 476 exp.DataType.Type.TINYBLOB: "BLOB", 477 exp.DataType.Type.INET: "INET", 478 exp.DataType.Type.ROWVERSION: "VARBINARY", 479 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 480 } 481 482 TIME_PART_SINGULARS = { 483 "MICROSECONDS": "MICROSECOND", 484 "SECONDS": "SECOND", 485 "MINUTES": "MINUTE", 486 "HOURS": "HOUR", 487 "DAYS": "DAY", 488 "WEEKS": "WEEK", 489 "MONTHS": "MONTH", 490 "QUARTERS": "QUARTER", 491 "YEARS": "YEAR", 492 } 493 494 AFTER_HAVING_MODIFIER_TRANSFORMS = { 495 "cluster": lambda self, e: self.sql(e, "cluster"), 496 "distribute": lambda self, e: self.sql(e, "distribute"), 497 "sort": lambda self, e: self.sql(e, "sort"), 498 "windows": lambda self, e: ( 499 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 500 if e.args.get("windows") 501 else "" 502 ), 503 "qualify": lambda self, e: self.sql(e, "qualify"), 504 } 505 506 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 507 508 STRUCT_DELIMITER = ("<", ">") 509 510 PARAMETER_TOKEN = "@" 511 NAMED_PLACEHOLDER_TOKEN = ":" 512 513 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 514 515 PROPERTIES_LOCATION = { 516 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 517 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 518 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 519 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 521 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 522 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 523 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 524 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 525 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 526 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 527 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 528 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 529 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 530 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 531 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 532 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 533 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 534 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 535 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 536 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 539 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 540 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 543 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 544 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 545 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 546 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 547 exp.HeapProperty: exp.Properties.Location.POST_WITH, 548 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 550 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 553 exp.JournalProperty: exp.Properties.Location.POST_NAME, 554 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 559 exp.LogProperty: exp.Properties.Location.POST_NAME, 560 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 561 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 562 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 563 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 565 exp.Order: exp.Properties.Location.POST_SCHEMA, 566 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 568 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 570 exp.Property: exp.Properties.Location.POST_WITH, 571 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 579 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 581 exp.Set: exp.Properties.Location.POST_SCHEMA, 582 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.SetProperty: exp.Properties.Location.POST_CREATE, 584 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 586 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 587 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 590 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 593 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.Tags: exp.Properties.Location.POST_WITH, 595 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 596 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 598 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 600 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 601 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 604 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 605 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 606 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 607 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 610 } 611 612 # Keywords that can't be used as unquoted identifier names 613 RESERVED_KEYWORDS: t.Set[str] = set() 614 615 # Expressions whose comments are separated from them for better formatting 616 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 617 exp.Command, 618 exp.Create, 619 exp.Describe, 620 exp.Delete, 621 exp.Drop, 622 exp.From, 623 exp.Insert, 624 exp.Join, 625 exp.MultitableInserts, 626 exp.Select, 627 exp.SetOperation, 628 exp.Update, 629 exp.Where, 630 exp.With, 631 ) 632 633 # Expressions that should not have their comments generated in maybe_comment 634 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 635 exp.Binary, 636 exp.SetOperation, 637 ) 638 639 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 640 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 641 exp.Column, 642 exp.Literal, 643 exp.Neg, 644 exp.Paren, 645 ) 646 647 PARAMETERIZABLE_TEXT_TYPES = { 648 exp.DataType.Type.NVARCHAR, 649 exp.DataType.Type.VARCHAR, 650 exp.DataType.Type.CHAR, 651 exp.DataType.Type.NCHAR, 652 } 653 654 # Expressions that need to have all CTEs under them bubbled up to them 655 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 656 657 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 658 659 __slots__ = ( 660 "pretty", 661 "identify", 662 "normalize", 663 "pad", 664 "_indent", 665 "normalize_functions", 666 "unsupported_level", 667 "max_unsupported", 668 "leading_comma", 669 "max_text_width", 670 "comments", 671 "dialect", 672 "unsupported_messages", 673 "_escaped_quote_end", 674 "_escaped_identifier_end", 675 "_next_name", 676 "_identifier_start", 677 "_identifier_end", 678 "_quote_json_path_key_using_brackets", 679 ) 680 681 def __init__( 682 self, 683 pretty: t.Optional[bool] = None, 684 identify: str | bool = False, 685 normalize: bool = False, 686 pad: int = 2, 687 indent: int = 2, 688 normalize_functions: t.Optional[str | bool] = None, 689 unsupported_level: ErrorLevel = ErrorLevel.WARN, 690 max_unsupported: int = 3, 691 leading_comma: bool = False, 692 max_text_width: int = 80, 693 comments: bool = True, 694 dialect: DialectType = None, 695 ): 696 import sqlglot 697 from sqlglot.dialects import Dialect 698 699 self.pretty = pretty if pretty is not None else sqlglot.pretty 700 self.identify = identify 701 self.normalize = normalize 702 self.pad = pad 703 self._indent = indent 704 self.unsupported_level = unsupported_level 705 self.max_unsupported = max_unsupported 706 self.leading_comma = leading_comma 707 self.max_text_width = max_text_width 708 self.comments = comments 709 self.dialect = Dialect.get_or_raise(dialect) 710 711 # This is both a Dialect property and a Generator argument, so we prioritize the latter 712 self.normalize_functions = ( 713 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 714 ) 715 716 self.unsupported_messages: t.List[str] = [] 717 self._escaped_quote_end: str = ( 718 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 719 ) 720 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 721 722 self._next_name = name_sequence("_t") 723 724 self._identifier_start = self.dialect.IDENTIFIER_START 725 self._identifier_end = self.dialect.IDENTIFIER_END 726 727 self._quote_json_path_key_using_brackets = True 728 729 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 730 """ 731 Generates the SQL string corresponding to the given syntax tree. 732 733 Args: 734 expression: The syntax tree. 735 copy: Whether to copy the expression. The generator performs mutations so 736 it is safer to copy. 737 738 Returns: 739 The SQL string corresponding to `expression`. 740 """ 741 if copy: 742 expression = expression.copy() 743 744 expression = self.preprocess(expression) 745 746 self.unsupported_messages = [] 747 sql = self.sql(expression).strip() 748 749 if self.pretty: 750 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 751 752 if self.unsupported_level == ErrorLevel.IGNORE: 753 return sql 754 755 if self.unsupported_level == ErrorLevel.WARN: 756 for msg in self.unsupported_messages: 757 logger.warning(msg) 758 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 759 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 760 761 return sql 762 763 def preprocess(self, expression: exp.Expression) -> exp.Expression: 764 """Apply generic preprocessing transformations to a given expression.""" 765 expression = self._move_ctes_to_top_level(expression) 766 767 if self.ENSURE_BOOLS: 768 from sqlglot.transforms import ensure_bools 769 770 expression = ensure_bools(expression) 771 772 return expression 773 774 def _move_ctes_to_top_level(self, expression: E) -> E: 775 if ( 776 not expression.parent 777 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 778 and any(node.parent is not expression for node in expression.find_all(exp.With)) 779 ): 780 from sqlglot.transforms import move_ctes_to_top_level 781 782 expression = move_ctes_to_top_level(expression) 783 return expression 784 785 def unsupported(self, message: str) -> None: 786 if self.unsupported_level == ErrorLevel.IMMEDIATE: 787 raise UnsupportedError(message) 788 self.unsupported_messages.append(message) 789 790 def sep(self, sep: str = " ") -> str: 791 return f"{sep.strip()}\n" if self.pretty else sep 792 793 def seg(self, sql: str, sep: str = " ") -> str: 794 return f"{self.sep(sep)}{sql}" 795 796 def pad_comment(self, comment: str) -> str: 797 comment = " " + comment if comment[0].strip() else comment 798 comment = comment + " " if comment[-1].strip() else comment 799 return comment 800 801 def maybe_comment( 802 self, 803 sql: str, 804 expression: t.Optional[exp.Expression] = None, 805 comments: t.Optional[t.List[str]] = None, 806 separated: bool = False, 807 ) -> str: 808 comments = ( 809 ((expression and expression.comments) if comments is None else comments) # type: ignore 810 if self.comments 811 else None 812 ) 813 814 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 815 return sql 816 817 comments_sql = " ".join( 818 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 819 ) 820 821 if not comments_sql: 822 return sql 823 824 comments_sql = self._replace_line_breaks(comments_sql) 825 826 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 827 return ( 828 f"{self.sep()}{comments_sql}{sql}" 829 if not sql or sql[0].isspace() 830 else f"{comments_sql}{self.sep()}{sql}" 831 ) 832 833 return f"{sql} {comments_sql}" 834 835 def wrap(self, expression: exp.Expression | str) -> str: 836 this_sql = ( 837 self.sql(expression) 838 if isinstance(expression, exp.UNWRAPPED_QUERIES) 839 else self.sql(expression, "this") 840 ) 841 if not this_sql: 842 return "()" 843 844 this_sql = self.indent(this_sql, level=1, pad=0) 845 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 846 847 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 848 original = self.identify 849 self.identify = False 850 result = func(*args, **kwargs) 851 self.identify = original 852 return result 853 854 def normalize_func(self, name: str) -> str: 855 if self.normalize_functions == "upper" or self.normalize_functions is True: 856 return name.upper() 857 if self.normalize_functions == "lower": 858 return name.lower() 859 return name 860 861 def indent( 862 self, 863 sql: str, 864 level: int = 0, 865 pad: t.Optional[int] = None, 866 skip_first: bool = False, 867 skip_last: bool = False, 868 ) -> str: 869 if not self.pretty or not sql: 870 return sql 871 872 pad = self.pad if pad is None else pad 873 lines = sql.split("\n") 874 875 return "\n".join( 876 ( 877 line 878 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 879 else f"{' ' * (level * self._indent + pad)}{line}" 880 ) 881 for i, line in enumerate(lines) 882 ) 883 884 def sql( 885 self, 886 expression: t.Optional[str | exp.Expression], 887 key: t.Optional[str] = None, 888 comment: bool = True, 889 ) -> str: 890 if not expression: 891 return "" 892 893 if isinstance(expression, str): 894 return expression 895 896 if key: 897 value = expression.args.get(key) 898 if value: 899 return self.sql(value) 900 return "" 901 902 transform = self.TRANSFORMS.get(expression.__class__) 903 904 if callable(transform): 905 sql = transform(self, expression) 906 elif isinstance(expression, exp.Expression): 907 exp_handler_name = f"{expression.key}_sql" 908 909 if hasattr(self, exp_handler_name): 910 sql = getattr(self, exp_handler_name)(expression) 911 elif isinstance(expression, exp.Func): 912 sql = self.function_fallback_sql(expression) 913 elif isinstance(expression, exp.Property): 914 sql = self.property_sql(expression) 915 else: 916 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 917 else: 918 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 919 920 return self.maybe_comment(sql, expression) if self.comments and comment else sql 921 922 def uncache_sql(self, expression: exp.Uncache) -> str: 923 table = self.sql(expression, "this") 924 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 925 return f"UNCACHE TABLE{exists_sql} {table}" 926 927 def cache_sql(self, expression: exp.Cache) -> str: 928 lazy = " LAZY" if expression.args.get("lazy") else "" 929 table = self.sql(expression, "this") 930 options = expression.args.get("options") 931 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 932 sql = self.sql(expression, "expression") 933 sql = f" AS{self.sep()}{sql}" if sql else "" 934 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 935 return self.prepend_ctes(expression, sql) 936 937 def characterset_sql(self, expression: exp.CharacterSet) -> str: 938 if isinstance(expression.parent, exp.Cast): 939 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 940 default = "DEFAULT " if expression.args.get("default") else "" 941 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 942 943 def column_parts(self, expression: exp.Column) -> str: 944 return ".".join( 945 self.sql(part) 946 for part in ( 947 expression.args.get("catalog"), 948 expression.args.get("db"), 949 expression.args.get("table"), 950 expression.args.get("this"), 951 ) 952 if part 953 ) 954 955 def column_sql(self, expression: exp.Column) -> str: 956 join_mark = " (+)" if expression.args.get("join_mark") else "" 957 958 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 959 join_mark = "" 960 self.unsupported("Outer join syntax using the (+) operator is not supported.") 961 962 return f"{self.column_parts(expression)}{join_mark}" 963 964 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 965 this = self.sql(expression, "this") 966 this = f" {this}" if this else "" 967 position = self.sql(expression, "position") 968 return f"{position}{this}" 969 970 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 971 column = self.sql(expression, "this") 972 kind = self.sql(expression, "kind") 973 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 974 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 975 kind = f"{sep}{kind}" if kind else "" 976 constraints = f" {constraints}" if constraints else "" 977 position = self.sql(expression, "position") 978 position = f" {position}" if position else "" 979 980 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 981 kind = "" 982 983 return f"{exists}{column}{kind}{constraints}{position}" 984 985 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 986 this = self.sql(expression, "this") 987 kind_sql = self.sql(expression, "kind").strip() 988 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 989 990 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 991 this = self.sql(expression, "this") 992 if expression.args.get("not_null"): 993 persisted = " PERSISTED NOT NULL" 994 elif expression.args.get("persisted"): 995 persisted = " PERSISTED" 996 else: 997 persisted = "" 998 return f"AS {this}{persisted}" 999 1000 def autoincrementcolumnconstraint_sql(self, _) -> str: 1001 return self.token_sql(TokenType.AUTO_INCREMENT) 1002 1003 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1004 if isinstance(expression.this, list): 1005 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1006 else: 1007 this = self.sql(expression, "this") 1008 1009 return f"COMPRESS {this}" 1010 1011 def generatedasidentitycolumnconstraint_sql( 1012 self, expression: exp.GeneratedAsIdentityColumnConstraint 1013 ) -> str: 1014 this = "" 1015 if expression.this is not None: 1016 on_null = " ON NULL" if expression.args.get("on_null") else "" 1017 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1018 1019 start = expression.args.get("start") 1020 start = f"START WITH {start}" if start else "" 1021 increment = expression.args.get("increment") 1022 increment = f" INCREMENT BY {increment}" if increment else "" 1023 minvalue = expression.args.get("minvalue") 1024 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1025 maxvalue = expression.args.get("maxvalue") 1026 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1027 cycle = expression.args.get("cycle") 1028 cycle_sql = "" 1029 1030 if cycle is not None: 1031 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1032 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1033 1034 sequence_opts = "" 1035 if start or increment or cycle_sql: 1036 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1037 sequence_opts = f" ({sequence_opts.strip()})" 1038 1039 expr = self.sql(expression, "expression") 1040 expr = f"({expr})" if expr else "IDENTITY" 1041 1042 return f"GENERATED{this} AS {expr}{sequence_opts}" 1043 1044 def generatedasrowcolumnconstraint_sql( 1045 self, expression: exp.GeneratedAsRowColumnConstraint 1046 ) -> str: 1047 start = "START" if expression.args.get("start") else "END" 1048 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1049 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1050 1051 def periodforsystemtimeconstraint_sql( 1052 self, expression: exp.PeriodForSystemTimeConstraint 1053 ) -> str: 1054 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1055 1056 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1057 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1058 1059 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1060 return f"AS {self.sql(expression, 'this')}" 1061 1062 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1063 desc = expression.args.get("desc") 1064 if desc is not None: 1065 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1066 return "PRIMARY KEY" 1067 1068 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1069 this = self.sql(expression, "this") 1070 this = f" {this}" if this else "" 1071 index_type = expression.args.get("index_type") 1072 index_type = f" USING {index_type}" if index_type else "" 1073 on_conflict = self.sql(expression, "on_conflict") 1074 on_conflict = f" {on_conflict}" if on_conflict else "" 1075 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1076 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1077 1078 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1079 return self.sql(expression, "this") 1080 1081 def create_sql(self, expression: exp.Create) -> str: 1082 kind = self.sql(expression, "kind") 1083 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1084 properties = expression.args.get("properties") 1085 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1086 1087 this = self.createable_sql(expression, properties_locs) 1088 1089 properties_sql = "" 1090 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1091 exp.Properties.Location.POST_WITH 1092 ): 1093 properties_sql = self.sql( 1094 exp.Properties( 1095 expressions=[ 1096 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1097 *properties_locs[exp.Properties.Location.POST_WITH], 1098 ] 1099 ) 1100 ) 1101 1102 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1103 properties_sql = self.sep() + properties_sql 1104 elif not self.pretty: 1105 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1106 properties_sql = f" {properties_sql}" 1107 1108 begin = " BEGIN" if expression.args.get("begin") else "" 1109 end = " END" if expression.args.get("end") else "" 1110 1111 expression_sql = self.sql(expression, "expression") 1112 if expression_sql: 1113 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1114 1115 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1116 postalias_props_sql = "" 1117 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1118 postalias_props_sql = self.properties( 1119 exp.Properties( 1120 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1121 ), 1122 wrapped=False, 1123 ) 1124 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1125 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1126 1127 postindex_props_sql = "" 1128 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1129 postindex_props_sql = self.properties( 1130 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1131 wrapped=False, 1132 prefix=" ", 1133 ) 1134 1135 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1136 indexes = f" {indexes}" if indexes else "" 1137 index_sql = indexes + postindex_props_sql 1138 1139 replace = " OR REPLACE" if expression.args.get("replace") else "" 1140 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1141 unique = " UNIQUE" if expression.args.get("unique") else "" 1142 1143 clustered = expression.args.get("clustered") 1144 if clustered is None: 1145 clustered_sql = "" 1146 elif clustered: 1147 clustered_sql = " CLUSTERED COLUMNSTORE" 1148 else: 1149 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1150 1151 postcreate_props_sql = "" 1152 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1153 postcreate_props_sql = self.properties( 1154 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1155 sep=" ", 1156 prefix=" ", 1157 wrapped=False, 1158 ) 1159 1160 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1161 1162 postexpression_props_sql = "" 1163 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1164 postexpression_props_sql = self.properties( 1165 exp.Properties( 1166 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1167 ), 1168 sep=" ", 1169 prefix=" ", 1170 wrapped=False, 1171 ) 1172 1173 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1174 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1175 no_schema_binding = ( 1176 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1177 ) 1178 1179 clone = self.sql(expression, "clone") 1180 clone = f" {clone}" if clone else "" 1181 1182 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1183 properties_expression = f"{expression_sql}{properties_sql}" 1184 else: 1185 properties_expression = f"{properties_sql}{expression_sql}" 1186 1187 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1188 return self.prepend_ctes(expression, expression_sql) 1189 1190 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1191 start = self.sql(expression, "start") 1192 start = f"START WITH {start}" if start else "" 1193 increment = self.sql(expression, "increment") 1194 increment = f" INCREMENT BY {increment}" if increment else "" 1195 minvalue = self.sql(expression, "minvalue") 1196 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1197 maxvalue = self.sql(expression, "maxvalue") 1198 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1199 owned = self.sql(expression, "owned") 1200 owned = f" OWNED BY {owned}" if owned else "" 1201 1202 cache = expression.args.get("cache") 1203 if cache is None: 1204 cache_str = "" 1205 elif cache is True: 1206 cache_str = " CACHE" 1207 else: 1208 cache_str = f" CACHE {cache}" 1209 1210 options = self.expressions(expression, key="options", flat=True, sep=" ") 1211 options = f" {options}" if options else "" 1212 1213 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1214 1215 def clone_sql(self, expression: exp.Clone) -> str: 1216 this = self.sql(expression, "this") 1217 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1218 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1219 return f"{shallow}{keyword} {this}" 1220 1221 def describe_sql(self, expression: exp.Describe) -> str: 1222 style = expression.args.get("style") 1223 style = f" {style}" if style else "" 1224 partition = self.sql(expression, "partition") 1225 partition = f" {partition}" if partition else "" 1226 format = self.sql(expression, "format") 1227 format = f" {format}" if format else "" 1228 1229 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1230 1231 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1232 tag = self.sql(expression, "tag") 1233 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1234 1235 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1236 with_ = self.sql(expression, "with") 1237 if with_: 1238 sql = f"{with_}{self.sep()}{sql}" 1239 return sql 1240 1241 def with_sql(self, expression: exp.With) -> str: 1242 sql = self.expressions(expression, flat=True) 1243 recursive = ( 1244 "RECURSIVE " 1245 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1246 else "" 1247 ) 1248 search = self.sql(expression, "search") 1249 search = f" {search}" if search else "" 1250 1251 return f"WITH {recursive}{sql}{search}" 1252 1253 def cte_sql(self, expression: exp.CTE) -> str: 1254 alias = expression.args.get("alias") 1255 if alias: 1256 alias.add_comments(expression.pop_comments()) 1257 1258 alias_sql = self.sql(expression, "alias") 1259 1260 materialized = expression.args.get("materialized") 1261 if materialized is False: 1262 materialized = "NOT MATERIALIZED " 1263 elif materialized: 1264 materialized = "MATERIALIZED " 1265 1266 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1267 1268 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1269 alias = self.sql(expression, "this") 1270 columns = self.expressions(expression, key="columns", flat=True) 1271 columns = f"({columns})" if columns else "" 1272 1273 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1274 columns = "" 1275 self.unsupported("Named columns are not supported in table alias.") 1276 1277 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1278 alias = self._next_name() 1279 1280 return f"{alias}{columns}" 1281 1282 def bitstring_sql(self, expression: exp.BitString) -> str: 1283 this = self.sql(expression, "this") 1284 if self.dialect.BIT_START: 1285 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1286 return f"{int(this, 2)}" 1287 1288 def hexstring_sql( 1289 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1290 ) -> str: 1291 this = self.sql(expression, "this") 1292 is_integer_type = expression.args.get("is_integer") 1293 1294 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1295 not self.dialect.HEX_START and not binary_function_repr 1296 ): 1297 # Integer representation will be returned if: 1298 # - The read dialect treats the hex value as integer literal but not the write 1299 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1300 return f"{int(this, 16)}" 1301 1302 if not is_integer_type: 1303 # Read dialect treats the hex value as BINARY/BLOB 1304 if binary_function_repr: 1305 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1306 return self.func(binary_function_repr, exp.Literal.string(this)) 1307 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1308 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1309 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1310 1311 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1312 1313 def bytestring_sql(self, expression: exp.ByteString) -> str: 1314 this = self.sql(expression, "this") 1315 if self.dialect.BYTE_START: 1316 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1317 return this 1318 1319 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1320 this = self.sql(expression, "this") 1321 escape = expression.args.get("escape") 1322 1323 if self.dialect.UNICODE_START: 1324 escape_substitute = r"\\\1" 1325 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1326 else: 1327 escape_substitute = r"\\u\1" 1328 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1329 1330 if escape: 1331 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1332 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1333 else: 1334 escape_pattern = ESCAPED_UNICODE_RE 1335 escape_sql = "" 1336 1337 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1338 this = escape_pattern.sub(escape_substitute, this) 1339 1340 return f"{left_quote}{this}{right_quote}{escape_sql}" 1341 1342 def rawstring_sql(self, expression: exp.RawString) -> str: 1343 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1344 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1345 1346 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1347 this = self.sql(expression, "this") 1348 specifier = self.sql(expression, "expression") 1349 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1350 return f"{this}{specifier}" 1351 1352 def datatype_sql(self, expression: exp.DataType) -> str: 1353 nested = "" 1354 values = "" 1355 interior = self.expressions(expression, flat=True) 1356 1357 type_value = expression.this 1358 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1359 type_sql = self.sql(expression, "kind") 1360 else: 1361 type_sql = ( 1362 self.TYPE_MAPPING.get(type_value, type_value.value) 1363 if isinstance(type_value, exp.DataType.Type) 1364 else type_value 1365 ) 1366 1367 if interior: 1368 if expression.args.get("nested"): 1369 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1370 if expression.args.get("values") is not None: 1371 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1372 values = self.expressions(expression, key="values", flat=True) 1373 values = f"{delimiters[0]}{values}{delimiters[1]}" 1374 elif type_value == exp.DataType.Type.INTERVAL: 1375 nested = f" {interior}" 1376 else: 1377 nested = f"({interior})" 1378 1379 type_sql = f"{type_sql}{nested}{values}" 1380 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1381 exp.DataType.Type.TIMETZ, 1382 exp.DataType.Type.TIMESTAMPTZ, 1383 ): 1384 type_sql = f"{type_sql} WITH TIME ZONE" 1385 1386 return type_sql 1387 1388 def directory_sql(self, expression: exp.Directory) -> str: 1389 local = "LOCAL " if expression.args.get("local") else "" 1390 row_format = self.sql(expression, "row_format") 1391 row_format = f" {row_format}" if row_format else "" 1392 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1393 1394 def delete_sql(self, expression: exp.Delete) -> str: 1395 this = self.sql(expression, "this") 1396 this = f" FROM {this}" if this else "" 1397 using = self.sql(expression, "using") 1398 using = f" USING {using}" if using else "" 1399 cluster = self.sql(expression, "cluster") 1400 cluster = f" {cluster}" if cluster else "" 1401 where = self.sql(expression, "where") 1402 returning = self.sql(expression, "returning") 1403 limit = self.sql(expression, "limit") 1404 tables = self.expressions(expression, key="tables") 1405 tables = f" {tables}" if tables else "" 1406 if self.RETURNING_END: 1407 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1408 else: 1409 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1410 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1411 1412 def drop_sql(self, expression: exp.Drop) -> str: 1413 this = self.sql(expression, "this") 1414 expressions = self.expressions(expression, flat=True) 1415 expressions = f" ({expressions})" if expressions else "" 1416 kind = expression.args["kind"] 1417 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1418 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1419 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1420 on_cluster = self.sql(expression, "cluster") 1421 on_cluster = f" {on_cluster}" if on_cluster else "" 1422 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1423 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1424 cascade = " CASCADE" if expression.args.get("cascade") else "" 1425 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1426 purge = " PURGE" if expression.args.get("purge") else "" 1427 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1428 1429 def set_operation(self, expression: exp.SetOperation) -> str: 1430 op_type = type(expression) 1431 op_name = op_type.key.upper() 1432 1433 distinct = expression.args.get("distinct") 1434 if ( 1435 distinct is False 1436 and op_type in (exp.Except, exp.Intersect) 1437 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1438 ): 1439 self.unsupported(f"{op_name} ALL is not supported") 1440 1441 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1442 1443 if distinct is None: 1444 distinct = default_distinct 1445 if distinct is None: 1446 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1447 1448 if distinct is default_distinct: 1449 distinct_or_all = "" 1450 else: 1451 distinct_or_all = " DISTINCT" if distinct else " ALL" 1452 1453 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1454 side_kind = f"{side_kind} " if side_kind else "" 1455 1456 by_name = " BY NAME" if expression.args.get("by_name") else "" 1457 on = self.expressions(expression, key="on", flat=True) 1458 on = f" ON ({on})" if on else "" 1459 1460 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1461 1462 def set_operations(self, expression: exp.SetOperation) -> str: 1463 if not self.SET_OP_MODIFIERS: 1464 limit = expression.args.get("limit") 1465 order = expression.args.get("order") 1466 1467 if limit or order: 1468 select = self._move_ctes_to_top_level( 1469 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1470 ) 1471 1472 if limit: 1473 select = select.limit(limit.pop(), copy=False) 1474 if order: 1475 select = select.order_by(order.pop(), copy=False) 1476 return self.sql(select) 1477 1478 sqls: t.List[str] = [] 1479 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1480 1481 while stack: 1482 node = stack.pop() 1483 1484 if isinstance(node, exp.SetOperation): 1485 stack.append(node.expression) 1486 stack.append( 1487 self.maybe_comment( 1488 self.set_operation(node), comments=node.comments, separated=True 1489 ) 1490 ) 1491 stack.append(node.this) 1492 else: 1493 sqls.append(self.sql(node)) 1494 1495 this = self.sep().join(sqls) 1496 this = self.query_modifiers(expression, this) 1497 return self.prepend_ctes(expression, this) 1498 1499 def fetch_sql(self, expression: exp.Fetch) -> str: 1500 direction = expression.args.get("direction") 1501 direction = f" {direction}" if direction else "" 1502 count = self.sql(expression, "count") 1503 count = f" {count}" if count else "" 1504 limit_options = self.sql(expression, "limit_options") 1505 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1506 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1507 1508 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1509 percent = " PERCENT" if expression.args.get("percent") else "" 1510 rows = " ROWS" if expression.args.get("rows") else "" 1511 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1512 if not with_ties and rows: 1513 with_ties = " ONLY" 1514 return f"{percent}{rows}{with_ties}" 1515 1516 def filter_sql(self, expression: exp.Filter) -> str: 1517 if self.AGGREGATE_FILTER_SUPPORTED: 1518 this = self.sql(expression, "this") 1519 where = self.sql(expression, "expression").strip() 1520 return f"{this} FILTER({where})" 1521 1522 agg = expression.this 1523 agg_arg = agg.this 1524 cond = expression.expression.this 1525 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1526 return self.sql(agg) 1527 1528 def hint_sql(self, expression: exp.Hint) -> str: 1529 if not self.QUERY_HINTS: 1530 self.unsupported("Hints are not supported") 1531 return "" 1532 1533 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1534 1535 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1536 using = self.sql(expression, "using") 1537 using = f" USING {using}" if using else "" 1538 columns = self.expressions(expression, key="columns", flat=True) 1539 columns = f"({columns})" if columns else "" 1540 partition_by = self.expressions(expression, key="partition_by", flat=True) 1541 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1542 where = self.sql(expression, "where") 1543 include = self.expressions(expression, key="include", flat=True) 1544 if include: 1545 include = f" INCLUDE ({include})" 1546 with_storage = self.expressions(expression, key="with_storage", flat=True) 1547 with_storage = f" WITH ({with_storage})" if with_storage else "" 1548 tablespace = self.sql(expression, "tablespace") 1549 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1550 on = self.sql(expression, "on") 1551 on = f" ON {on}" if on else "" 1552 1553 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1554 1555 def index_sql(self, expression: exp.Index) -> str: 1556 unique = "UNIQUE " if expression.args.get("unique") else "" 1557 primary = "PRIMARY " if expression.args.get("primary") else "" 1558 amp = "AMP " if expression.args.get("amp") else "" 1559 name = self.sql(expression, "this") 1560 name = f"{name} " if name else "" 1561 table = self.sql(expression, "table") 1562 table = f"{self.INDEX_ON} {table}" if table else "" 1563 1564 index = "INDEX " if not table else "" 1565 1566 params = self.sql(expression, "params") 1567 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1568 1569 def identifier_sql(self, expression: exp.Identifier) -> str: 1570 text = expression.name 1571 lower = text.lower() 1572 text = lower if self.normalize and not expression.quoted else text 1573 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1574 if ( 1575 expression.quoted 1576 or self.dialect.can_identify(text, self.identify) 1577 or lower in self.RESERVED_KEYWORDS 1578 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1579 ): 1580 text = f"{self._identifier_start}{text}{self._identifier_end}" 1581 return text 1582 1583 def hex_sql(self, expression: exp.Hex) -> str: 1584 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1585 if self.dialect.HEX_LOWERCASE: 1586 text = self.func("LOWER", text) 1587 1588 return text 1589 1590 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1591 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1592 if not self.dialect.HEX_LOWERCASE: 1593 text = self.func("LOWER", text) 1594 return text 1595 1596 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1597 input_format = self.sql(expression, "input_format") 1598 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1599 output_format = self.sql(expression, "output_format") 1600 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1601 return self.sep().join((input_format, output_format)) 1602 1603 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1604 string = self.sql(exp.Literal.string(expression.name)) 1605 return f"{prefix}{string}" 1606 1607 def partition_sql(self, expression: exp.Partition) -> str: 1608 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1609 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1610 1611 def properties_sql(self, expression: exp.Properties) -> str: 1612 root_properties = [] 1613 with_properties = [] 1614 1615 for p in expression.expressions: 1616 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1617 if p_loc == exp.Properties.Location.POST_WITH: 1618 with_properties.append(p) 1619 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1620 root_properties.append(p) 1621 1622 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1623 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1624 1625 if root_props and with_props and not self.pretty: 1626 with_props = " " + with_props 1627 1628 return root_props + with_props 1629 1630 def root_properties(self, properties: exp.Properties) -> str: 1631 if properties.expressions: 1632 return self.expressions(properties, indent=False, sep=" ") 1633 return "" 1634 1635 def properties( 1636 self, 1637 properties: exp.Properties, 1638 prefix: str = "", 1639 sep: str = ", ", 1640 suffix: str = "", 1641 wrapped: bool = True, 1642 ) -> str: 1643 if properties.expressions: 1644 expressions = self.expressions(properties, sep=sep, indent=False) 1645 if expressions: 1646 expressions = self.wrap(expressions) if wrapped else expressions 1647 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1648 return "" 1649 1650 def with_properties(self, properties: exp.Properties) -> str: 1651 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1652 1653 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1654 properties_locs = defaultdict(list) 1655 for p in properties.expressions: 1656 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1657 if p_loc != exp.Properties.Location.UNSUPPORTED: 1658 properties_locs[p_loc].append(p) 1659 else: 1660 self.unsupported(f"Unsupported property {p.key}") 1661 1662 return properties_locs 1663 1664 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1665 if isinstance(expression.this, exp.Dot): 1666 return self.sql(expression, "this") 1667 return f"'{expression.name}'" if string_key else expression.name 1668 1669 def property_sql(self, expression: exp.Property) -> str: 1670 property_cls = expression.__class__ 1671 if property_cls == exp.Property: 1672 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1673 1674 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1675 if not property_name: 1676 self.unsupported(f"Unsupported property {expression.key}") 1677 1678 return f"{property_name}={self.sql(expression, 'this')}" 1679 1680 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1681 if self.SUPPORTS_CREATE_TABLE_LIKE: 1682 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1683 options = f" {options}" if options else "" 1684 1685 like = f"LIKE {self.sql(expression, 'this')}{options}" 1686 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1687 like = f"({like})" 1688 1689 return like 1690 1691 if expression.expressions: 1692 self.unsupported("Transpilation of LIKE property options is unsupported") 1693 1694 select = exp.select("*").from_(expression.this).limit(0) 1695 return f"AS {self.sql(select)}" 1696 1697 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1698 no = "NO " if expression.args.get("no") else "" 1699 protection = " PROTECTION" if expression.args.get("protection") else "" 1700 return f"{no}FALLBACK{protection}" 1701 1702 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1703 no = "NO " if expression.args.get("no") else "" 1704 local = expression.args.get("local") 1705 local = f"{local} " if local else "" 1706 dual = "DUAL " if expression.args.get("dual") else "" 1707 before = "BEFORE " if expression.args.get("before") else "" 1708 after = "AFTER " if expression.args.get("after") else "" 1709 return f"{no}{local}{dual}{before}{after}JOURNAL" 1710 1711 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1712 freespace = self.sql(expression, "this") 1713 percent = " PERCENT" if expression.args.get("percent") else "" 1714 return f"FREESPACE={freespace}{percent}" 1715 1716 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1717 if expression.args.get("default"): 1718 property = "DEFAULT" 1719 elif expression.args.get("on"): 1720 property = "ON" 1721 else: 1722 property = "OFF" 1723 return f"CHECKSUM={property}" 1724 1725 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1726 if expression.args.get("no"): 1727 return "NO MERGEBLOCKRATIO" 1728 if expression.args.get("default"): 1729 return "DEFAULT MERGEBLOCKRATIO" 1730 1731 percent = " PERCENT" if expression.args.get("percent") else "" 1732 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1733 1734 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1735 default = expression.args.get("default") 1736 minimum = expression.args.get("minimum") 1737 maximum = expression.args.get("maximum") 1738 if default or minimum or maximum: 1739 if default: 1740 prop = "DEFAULT" 1741 elif minimum: 1742 prop = "MINIMUM" 1743 else: 1744 prop = "MAXIMUM" 1745 return f"{prop} DATABLOCKSIZE" 1746 units = expression.args.get("units") 1747 units = f" {units}" if units else "" 1748 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1749 1750 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1751 autotemp = expression.args.get("autotemp") 1752 always = expression.args.get("always") 1753 default = expression.args.get("default") 1754 manual = expression.args.get("manual") 1755 never = expression.args.get("never") 1756 1757 if autotemp is not None: 1758 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1759 elif always: 1760 prop = "ALWAYS" 1761 elif default: 1762 prop = "DEFAULT" 1763 elif manual: 1764 prop = "MANUAL" 1765 elif never: 1766 prop = "NEVER" 1767 return f"BLOCKCOMPRESSION={prop}" 1768 1769 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1770 no = expression.args.get("no") 1771 no = " NO" if no else "" 1772 concurrent = expression.args.get("concurrent") 1773 concurrent = " CONCURRENT" if concurrent else "" 1774 target = self.sql(expression, "target") 1775 target = f" {target}" if target else "" 1776 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1777 1778 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1779 if isinstance(expression.this, list): 1780 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1781 if expression.this: 1782 modulus = self.sql(expression, "this") 1783 remainder = self.sql(expression, "expression") 1784 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1785 1786 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1787 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1788 return f"FROM ({from_expressions}) TO ({to_expressions})" 1789 1790 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1791 this = self.sql(expression, "this") 1792 1793 for_values_or_default = expression.expression 1794 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1795 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1796 else: 1797 for_values_or_default = " DEFAULT" 1798 1799 return f"PARTITION OF {this}{for_values_or_default}" 1800 1801 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1802 kind = expression.args.get("kind") 1803 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1804 for_or_in = expression.args.get("for_or_in") 1805 for_or_in = f" {for_or_in}" if for_or_in else "" 1806 lock_type = expression.args.get("lock_type") 1807 override = " OVERRIDE" if expression.args.get("override") else "" 1808 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1809 1810 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1811 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1812 statistics = expression.args.get("statistics") 1813 statistics_sql = "" 1814 if statistics is not None: 1815 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1816 return f"{data_sql}{statistics_sql}" 1817 1818 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1819 this = self.sql(expression, "this") 1820 this = f"HISTORY_TABLE={this}" if this else "" 1821 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1822 data_consistency = ( 1823 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1824 ) 1825 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1826 retention_period = ( 1827 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1828 ) 1829 1830 if this: 1831 on_sql = self.func("ON", this, data_consistency, retention_period) 1832 else: 1833 on_sql = "ON" if expression.args.get("on") else "OFF" 1834 1835 sql = f"SYSTEM_VERSIONING={on_sql}" 1836 1837 return f"WITH({sql})" if expression.args.get("with") else sql 1838 1839 def insert_sql(self, expression: exp.Insert) -> str: 1840 hint = self.sql(expression, "hint") 1841 overwrite = expression.args.get("overwrite") 1842 1843 if isinstance(expression.this, exp.Directory): 1844 this = " OVERWRITE" if overwrite else " INTO" 1845 else: 1846 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1847 1848 stored = self.sql(expression, "stored") 1849 stored = f" {stored}" if stored else "" 1850 alternative = expression.args.get("alternative") 1851 alternative = f" OR {alternative}" if alternative else "" 1852 ignore = " IGNORE" if expression.args.get("ignore") else "" 1853 is_function = expression.args.get("is_function") 1854 if is_function: 1855 this = f"{this} FUNCTION" 1856 this = f"{this} {self.sql(expression, 'this')}" 1857 1858 exists = " IF EXISTS" if expression.args.get("exists") else "" 1859 where = self.sql(expression, "where") 1860 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1861 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1862 on_conflict = self.sql(expression, "conflict") 1863 on_conflict = f" {on_conflict}" if on_conflict else "" 1864 by_name = " BY NAME" if expression.args.get("by_name") else "" 1865 returning = self.sql(expression, "returning") 1866 1867 if self.RETURNING_END: 1868 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1869 else: 1870 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1871 1872 partition_by = self.sql(expression, "partition") 1873 partition_by = f" {partition_by}" if partition_by else "" 1874 settings = self.sql(expression, "settings") 1875 settings = f" {settings}" if settings else "" 1876 1877 source = self.sql(expression, "source") 1878 source = f"TABLE {source}" if source else "" 1879 1880 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1881 return self.prepend_ctes(expression, sql) 1882 1883 def introducer_sql(self, expression: exp.Introducer) -> str: 1884 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1885 1886 def kill_sql(self, expression: exp.Kill) -> str: 1887 kind = self.sql(expression, "kind") 1888 kind = f" {kind}" if kind else "" 1889 this = self.sql(expression, "this") 1890 this = f" {this}" if this else "" 1891 return f"KILL{kind}{this}" 1892 1893 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1894 return expression.name 1895 1896 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1897 return expression.name 1898 1899 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1900 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1901 1902 constraint = self.sql(expression, "constraint") 1903 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1904 1905 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1906 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1907 action = self.sql(expression, "action") 1908 1909 expressions = self.expressions(expression, flat=True) 1910 if expressions: 1911 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1912 expressions = f" {set_keyword}{expressions}" 1913 1914 where = self.sql(expression, "where") 1915 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1916 1917 def returning_sql(self, expression: exp.Returning) -> str: 1918 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1919 1920 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1921 fields = self.sql(expression, "fields") 1922 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1923 escaped = self.sql(expression, "escaped") 1924 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1925 items = self.sql(expression, "collection_items") 1926 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1927 keys = self.sql(expression, "map_keys") 1928 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1929 lines = self.sql(expression, "lines") 1930 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1931 null = self.sql(expression, "null") 1932 null = f" NULL DEFINED AS {null}" if null else "" 1933 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1934 1935 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1936 return f"WITH ({self.expressions(expression, flat=True)})" 1937 1938 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1939 this = f"{self.sql(expression, 'this')} INDEX" 1940 target = self.sql(expression, "target") 1941 target = f" FOR {target}" if target else "" 1942 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1943 1944 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1945 this = self.sql(expression, "this") 1946 kind = self.sql(expression, "kind") 1947 expr = self.sql(expression, "expression") 1948 return f"{this} ({kind} => {expr})" 1949 1950 def table_parts(self, expression: exp.Table) -> str: 1951 return ".".join( 1952 self.sql(part) 1953 for part in ( 1954 expression.args.get("catalog"), 1955 expression.args.get("db"), 1956 expression.args.get("this"), 1957 ) 1958 if part is not None 1959 ) 1960 1961 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1962 table = self.table_parts(expression) 1963 only = "ONLY " if expression.args.get("only") else "" 1964 partition = self.sql(expression, "partition") 1965 partition = f" {partition}" if partition else "" 1966 version = self.sql(expression, "version") 1967 version = f" {version}" if version else "" 1968 alias = self.sql(expression, "alias") 1969 alias = f"{sep}{alias}" if alias else "" 1970 1971 sample = self.sql(expression, "sample") 1972 if self.dialect.ALIAS_POST_TABLESAMPLE: 1973 sample_pre_alias = sample 1974 sample_post_alias = "" 1975 else: 1976 sample_pre_alias = "" 1977 sample_post_alias = sample 1978 1979 hints = self.expressions(expression, key="hints", sep=" ") 1980 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1981 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1982 joins = self.indent( 1983 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1984 ) 1985 laterals = self.expressions(expression, key="laterals", sep="") 1986 1987 file_format = self.sql(expression, "format") 1988 if file_format: 1989 pattern = self.sql(expression, "pattern") 1990 pattern = f", PATTERN => {pattern}" if pattern else "" 1991 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1992 1993 ordinality = expression.args.get("ordinality") or "" 1994 if ordinality: 1995 ordinality = f" WITH ORDINALITY{alias}" 1996 alias = "" 1997 1998 when = self.sql(expression, "when") 1999 if when: 2000 table = f"{table} {when}" 2001 2002 changes = self.sql(expression, "changes") 2003 changes = f" {changes}" if changes else "" 2004 2005 rows_from = self.expressions(expression, key="rows_from") 2006 if rows_from: 2007 table = f"ROWS FROM {self.wrap(rows_from)}" 2008 2009 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2010 2011 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2012 table = self.func("TABLE", expression.this) 2013 alias = self.sql(expression, "alias") 2014 alias = f" AS {alias}" if alias else "" 2015 sample = self.sql(expression, "sample") 2016 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2017 joins = self.indent( 2018 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2019 ) 2020 return f"{table}{alias}{pivots}{sample}{joins}" 2021 2022 def tablesample_sql( 2023 self, 2024 expression: exp.TableSample, 2025 tablesample_keyword: t.Optional[str] = None, 2026 ) -> str: 2027 method = self.sql(expression, "method") 2028 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2029 numerator = self.sql(expression, "bucket_numerator") 2030 denominator = self.sql(expression, "bucket_denominator") 2031 field = self.sql(expression, "bucket_field") 2032 field = f" ON {field}" if field else "" 2033 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2034 seed = self.sql(expression, "seed") 2035 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2036 2037 size = self.sql(expression, "size") 2038 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2039 size = f"{size} ROWS" 2040 2041 percent = self.sql(expression, "percent") 2042 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2043 percent = f"{percent} PERCENT" 2044 2045 expr = f"{bucket}{percent}{size}" 2046 if self.TABLESAMPLE_REQUIRES_PARENS: 2047 expr = f"({expr})" 2048 2049 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2050 2051 def pivot_sql(self, expression: exp.Pivot) -> str: 2052 expressions = self.expressions(expression, flat=True) 2053 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2054 2055 group = self.sql(expression, "group") 2056 2057 if expression.this: 2058 this = self.sql(expression, "this") 2059 if not expressions: 2060 return f"UNPIVOT {this}" 2061 2062 on = f"{self.seg('ON')} {expressions}" 2063 into = self.sql(expression, "into") 2064 into = f"{self.seg('INTO')} {into}" if into else "" 2065 using = self.expressions(expression, key="using", flat=True) 2066 using = f"{self.seg('USING')} {using}" if using else "" 2067 return f"{direction} {this}{on}{into}{using}{group}" 2068 2069 alias = self.sql(expression, "alias") 2070 alias = f" AS {alias}" if alias else "" 2071 2072 fields = self.expressions( 2073 expression, 2074 "fields", 2075 sep=" ", 2076 dynamic=True, 2077 new_line=True, 2078 skip_first=True, 2079 skip_last=True, 2080 ) 2081 2082 include_nulls = expression.args.get("include_nulls") 2083 if include_nulls is not None: 2084 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2085 else: 2086 nulls = "" 2087 2088 default_on_null = self.sql(expression, "default_on_null") 2089 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2090 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2091 2092 def version_sql(self, expression: exp.Version) -> str: 2093 this = f"FOR {expression.name}" 2094 kind = expression.text("kind") 2095 expr = self.sql(expression, "expression") 2096 return f"{this} {kind} {expr}" 2097 2098 def tuple_sql(self, expression: exp.Tuple) -> str: 2099 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2100 2101 def update_sql(self, expression: exp.Update) -> str: 2102 this = self.sql(expression, "this") 2103 set_sql = self.expressions(expression, flat=True) 2104 from_sql = self.sql(expression, "from") 2105 where_sql = self.sql(expression, "where") 2106 returning = self.sql(expression, "returning") 2107 order = self.sql(expression, "order") 2108 limit = self.sql(expression, "limit") 2109 if self.RETURNING_END: 2110 expression_sql = f"{from_sql}{where_sql}{returning}" 2111 else: 2112 expression_sql = f"{returning}{from_sql}{where_sql}" 2113 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2114 return self.prepend_ctes(expression, sql) 2115 2116 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2117 values_as_table = values_as_table and self.VALUES_AS_TABLE 2118 2119 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2120 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2121 args = self.expressions(expression) 2122 alias = self.sql(expression, "alias") 2123 values = f"VALUES{self.seg('')}{args}" 2124 values = ( 2125 f"({values})" 2126 if self.WRAP_DERIVED_VALUES 2127 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2128 else values 2129 ) 2130 return f"{values} AS {alias}" if alias else values 2131 2132 # Converts `VALUES...` expression into a series of select unions. 2133 alias_node = expression.args.get("alias") 2134 column_names = alias_node and alias_node.columns 2135 2136 selects: t.List[exp.Query] = [] 2137 2138 for i, tup in enumerate(expression.expressions): 2139 row = tup.expressions 2140 2141 if i == 0 and column_names: 2142 row = [ 2143 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2144 ] 2145 2146 selects.append(exp.Select(expressions=row)) 2147 2148 if self.pretty: 2149 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2150 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2151 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2152 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2153 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2154 2155 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2156 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2157 return f"({unions}){alias}" 2158 2159 def var_sql(self, expression: exp.Var) -> str: 2160 return self.sql(expression, "this") 2161 2162 @unsupported_args("expressions") 2163 def into_sql(self, expression: exp.Into) -> str: 2164 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2165 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2166 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2167 2168 def from_sql(self, expression: exp.From) -> str: 2169 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2170 2171 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2172 grouping_sets = self.expressions(expression, indent=False) 2173 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2174 2175 def rollup_sql(self, expression: exp.Rollup) -> str: 2176 expressions = self.expressions(expression, indent=False) 2177 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2178 2179 def cube_sql(self, expression: exp.Cube) -> str: 2180 expressions = self.expressions(expression, indent=False) 2181 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2182 2183 def group_sql(self, expression: exp.Group) -> str: 2184 group_by_all = expression.args.get("all") 2185 if group_by_all is True: 2186 modifier = " ALL" 2187 elif group_by_all is False: 2188 modifier = " DISTINCT" 2189 else: 2190 modifier = "" 2191 2192 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2193 2194 grouping_sets = self.expressions(expression, key="grouping_sets") 2195 cube = self.expressions(expression, key="cube") 2196 rollup = self.expressions(expression, key="rollup") 2197 2198 groupings = csv( 2199 self.seg(grouping_sets) if grouping_sets else "", 2200 self.seg(cube) if cube else "", 2201 self.seg(rollup) if rollup else "", 2202 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2203 sep=self.GROUPINGS_SEP, 2204 ) 2205 2206 if ( 2207 expression.expressions 2208 and groupings 2209 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2210 ): 2211 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2212 2213 return f"{group_by}{groupings}" 2214 2215 def having_sql(self, expression: exp.Having) -> str: 2216 this = self.indent(self.sql(expression, "this")) 2217 return f"{self.seg('HAVING')}{self.sep()}{this}" 2218 2219 def connect_sql(self, expression: exp.Connect) -> str: 2220 start = self.sql(expression, "start") 2221 start = self.seg(f"START WITH {start}") if start else "" 2222 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2223 connect = self.sql(expression, "connect") 2224 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2225 return start + connect 2226 2227 def prior_sql(self, expression: exp.Prior) -> str: 2228 return f"PRIOR {self.sql(expression, 'this')}" 2229 2230 def join_sql(self, expression: exp.Join) -> str: 2231 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2232 side = None 2233 else: 2234 side = expression.side 2235 2236 op_sql = " ".join( 2237 op 2238 for op in ( 2239 expression.method, 2240 "GLOBAL" if expression.args.get("global") else None, 2241 side, 2242 expression.kind, 2243 expression.hint if self.JOIN_HINTS else None, 2244 ) 2245 if op 2246 ) 2247 match_cond = self.sql(expression, "match_condition") 2248 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2249 on_sql = self.sql(expression, "on") 2250 using = expression.args.get("using") 2251 2252 if not on_sql and using: 2253 on_sql = csv(*(self.sql(column) for column in using)) 2254 2255 this = expression.this 2256 this_sql = self.sql(this) 2257 2258 exprs = self.expressions(expression) 2259 if exprs: 2260 this_sql = f"{this_sql},{self.seg(exprs)}" 2261 2262 if on_sql: 2263 on_sql = self.indent(on_sql, skip_first=True) 2264 space = self.seg(" " * self.pad) if self.pretty else " " 2265 if using: 2266 on_sql = f"{space}USING ({on_sql})" 2267 else: 2268 on_sql = f"{space}ON {on_sql}" 2269 elif not op_sql: 2270 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2271 return f" {this_sql}" 2272 2273 return f", {this_sql}" 2274 2275 if op_sql != "STRAIGHT_JOIN": 2276 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2277 2278 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2279 2280 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2281 args = self.expressions(expression, flat=True) 2282 args = f"({args})" if len(args.split(",")) > 1 else args 2283 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2284 2285 def lateral_op(self, expression: exp.Lateral) -> str: 2286 cross_apply = expression.args.get("cross_apply") 2287 2288 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2289 if cross_apply is True: 2290 op = "INNER JOIN " 2291 elif cross_apply is False: 2292 op = "LEFT JOIN " 2293 else: 2294 op = "" 2295 2296 return f"{op}LATERAL" 2297 2298 def lateral_sql(self, expression: exp.Lateral) -> str: 2299 this = self.sql(expression, "this") 2300 2301 if expression.args.get("view"): 2302 alias = expression.args["alias"] 2303 columns = self.expressions(alias, key="columns", flat=True) 2304 table = f" {alias.name}" if alias.name else "" 2305 columns = f" AS {columns}" if columns else "" 2306 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2307 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2308 2309 alias = self.sql(expression, "alias") 2310 alias = f" AS {alias}" if alias else "" 2311 2312 ordinality = expression.args.get("ordinality") or "" 2313 if ordinality: 2314 ordinality = f" WITH ORDINALITY{alias}" 2315 alias = "" 2316 2317 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2318 2319 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2320 this = self.sql(expression, "this") 2321 2322 args = [ 2323 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2324 for e in (expression.args.get(k) for k in ("offset", "expression")) 2325 if e 2326 ] 2327 2328 args_sql = ", ".join(self.sql(e) for e in args) 2329 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2330 expressions = self.expressions(expression, flat=True) 2331 limit_options = self.sql(expression, "limit_options") 2332 expressions = f" BY {expressions}" if expressions else "" 2333 2334 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2335 2336 def offset_sql(self, expression: exp.Offset) -> str: 2337 this = self.sql(expression, "this") 2338 value = expression.expression 2339 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2340 expressions = self.expressions(expression, flat=True) 2341 expressions = f" BY {expressions}" if expressions else "" 2342 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2343 2344 def setitem_sql(self, expression: exp.SetItem) -> str: 2345 kind = self.sql(expression, "kind") 2346 kind = f"{kind} " if kind else "" 2347 this = self.sql(expression, "this") 2348 expressions = self.expressions(expression) 2349 collate = self.sql(expression, "collate") 2350 collate = f" COLLATE {collate}" if collate else "" 2351 global_ = "GLOBAL " if expression.args.get("global") else "" 2352 return f"{global_}{kind}{this}{expressions}{collate}" 2353 2354 def set_sql(self, expression: exp.Set) -> str: 2355 expressions = f" {self.expressions(expression, flat=True)}" 2356 tag = " TAG" if expression.args.get("tag") else "" 2357 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2358 2359 def pragma_sql(self, expression: exp.Pragma) -> str: 2360 return f"PRAGMA {self.sql(expression, 'this')}" 2361 2362 def lock_sql(self, expression: exp.Lock) -> str: 2363 if not self.LOCKING_READS_SUPPORTED: 2364 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2365 return "" 2366 2367 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2368 expressions = self.expressions(expression, flat=True) 2369 expressions = f" OF {expressions}" if expressions else "" 2370 wait = expression.args.get("wait") 2371 2372 if wait is not None: 2373 if isinstance(wait, exp.Literal): 2374 wait = f" WAIT {self.sql(wait)}" 2375 else: 2376 wait = " NOWAIT" if wait else " SKIP LOCKED" 2377 2378 return f"{lock_type}{expressions}{wait or ''}" 2379 2380 def literal_sql(self, expression: exp.Literal) -> str: 2381 text = expression.this or "" 2382 if expression.is_string: 2383 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2384 return text 2385 2386 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2387 if self.dialect.ESCAPED_SEQUENCES: 2388 to_escaped = self.dialect.ESCAPED_SEQUENCES 2389 text = "".join( 2390 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2391 ) 2392 2393 return self._replace_line_breaks(text).replace( 2394 self.dialect.QUOTE_END, self._escaped_quote_end 2395 ) 2396 2397 def loaddata_sql(self, expression: exp.LoadData) -> str: 2398 local = " LOCAL" if expression.args.get("local") else "" 2399 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2400 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2401 this = f" INTO TABLE {self.sql(expression, 'this')}" 2402 partition = self.sql(expression, "partition") 2403 partition = f" {partition}" if partition else "" 2404 input_format = self.sql(expression, "input_format") 2405 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2406 serde = self.sql(expression, "serde") 2407 serde = f" SERDE {serde}" if serde else "" 2408 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2409 2410 def null_sql(self, *_) -> str: 2411 return "NULL" 2412 2413 def boolean_sql(self, expression: exp.Boolean) -> str: 2414 return "TRUE" if expression.this else "FALSE" 2415 2416 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2417 this = self.sql(expression, "this") 2418 this = f"{this} " if this else this 2419 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2420 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2421 2422 def withfill_sql(self, expression: exp.WithFill) -> str: 2423 from_sql = self.sql(expression, "from") 2424 from_sql = f" FROM {from_sql}" if from_sql else "" 2425 to_sql = self.sql(expression, "to") 2426 to_sql = f" TO {to_sql}" if to_sql else "" 2427 step_sql = self.sql(expression, "step") 2428 step_sql = f" STEP {step_sql}" if step_sql else "" 2429 interpolated_values = [ 2430 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2431 if isinstance(e, exp.Alias) 2432 else self.sql(e, "this") 2433 for e in expression.args.get("interpolate") or [] 2434 ] 2435 interpolate = ( 2436 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2437 ) 2438 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2439 2440 def cluster_sql(self, expression: exp.Cluster) -> str: 2441 return self.op_expressions("CLUSTER BY", expression) 2442 2443 def distribute_sql(self, expression: exp.Distribute) -> str: 2444 return self.op_expressions("DISTRIBUTE BY", expression) 2445 2446 def sort_sql(self, expression: exp.Sort) -> str: 2447 return self.op_expressions("SORT BY", expression) 2448 2449 def ordered_sql(self, expression: exp.Ordered) -> str: 2450 desc = expression.args.get("desc") 2451 asc = not desc 2452 2453 nulls_first = expression.args.get("nulls_first") 2454 nulls_last = not nulls_first 2455 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2456 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2457 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2458 2459 this = self.sql(expression, "this") 2460 2461 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2462 nulls_sort_change = "" 2463 if nulls_first and ( 2464 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2465 ): 2466 nulls_sort_change = " NULLS FIRST" 2467 elif ( 2468 nulls_last 2469 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2470 and not nulls_are_last 2471 ): 2472 nulls_sort_change = " NULLS LAST" 2473 2474 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2475 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2476 window = expression.find_ancestor(exp.Window, exp.Select) 2477 if isinstance(window, exp.Window) and window.args.get("spec"): 2478 self.unsupported( 2479 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2480 ) 2481 nulls_sort_change = "" 2482 elif self.NULL_ORDERING_SUPPORTED is False and ( 2483 (asc and nulls_sort_change == " NULLS LAST") 2484 or (desc and nulls_sort_change == " NULLS FIRST") 2485 ): 2486 # BigQuery does not allow these ordering/nulls combinations when used under 2487 # an aggregation func or under a window containing one 2488 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2489 2490 if isinstance(ancestor, exp.Window): 2491 ancestor = ancestor.this 2492 if isinstance(ancestor, exp.AggFunc): 2493 self.unsupported( 2494 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2495 ) 2496 nulls_sort_change = "" 2497 elif self.NULL_ORDERING_SUPPORTED is None: 2498 if expression.this.is_int: 2499 self.unsupported( 2500 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2501 ) 2502 elif not isinstance(expression.this, exp.Rand): 2503 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2504 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2505 nulls_sort_change = "" 2506 2507 with_fill = self.sql(expression, "with_fill") 2508 with_fill = f" {with_fill}" if with_fill else "" 2509 2510 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2511 2512 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2513 window_frame = self.sql(expression, "window_frame") 2514 window_frame = f"{window_frame} " if window_frame else "" 2515 2516 this = self.sql(expression, "this") 2517 2518 return f"{window_frame}{this}" 2519 2520 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2521 partition = self.partition_by_sql(expression) 2522 order = self.sql(expression, "order") 2523 measures = self.expressions(expression, key="measures") 2524 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2525 rows = self.sql(expression, "rows") 2526 rows = self.seg(rows) if rows else "" 2527 after = self.sql(expression, "after") 2528 after = self.seg(after) if after else "" 2529 pattern = self.sql(expression, "pattern") 2530 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2531 definition_sqls = [ 2532 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2533 for definition in expression.args.get("define", []) 2534 ] 2535 definitions = self.expressions(sqls=definition_sqls) 2536 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2537 body = "".join( 2538 ( 2539 partition, 2540 order, 2541 measures, 2542 rows, 2543 after, 2544 pattern, 2545 define, 2546 ) 2547 ) 2548 alias = self.sql(expression, "alias") 2549 alias = f" {alias}" if alias else "" 2550 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2551 2552 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2553 limit = expression.args.get("limit") 2554 2555 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2556 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2557 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2558 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2559 2560 return csv( 2561 *sqls, 2562 *[self.sql(join) for join in expression.args.get("joins") or []], 2563 self.sql(expression, "match"), 2564 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2565 self.sql(expression, "prewhere"), 2566 self.sql(expression, "where"), 2567 self.sql(expression, "connect"), 2568 self.sql(expression, "group"), 2569 self.sql(expression, "having"), 2570 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2571 self.sql(expression, "order"), 2572 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2573 *self.after_limit_modifiers(expression), 2574 self.options_modifier(expression), 2575 sep="", 2576 ) 2577 2578 def options_modifier(self, expression: exp.Expression) -> str: 2579 options = self.expressions(expression, key="options") 2580 return f" {options}" if options else "" 2581 2582 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2583 return "" 2584 2585 def offset_limit_modifiers( 2586 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2587 ) -> t.List[str]: 2588 return [ 2589 self.sql(expression, "offset") if fetch else self.sql(limit), 2590 self.sql(limit) if fetch else self.sql(expression, "offset"), 2591 ] 2592 2593 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2594 locks = self.expressions(expression, key="locks", sep=" ") 2595 locks = f" {locks}" if locks else "" 2596 return [locks, self.sql(expression, "sample")] 2597 2598 def select_sql(self, expression: exp.Select) -> str: 2599 into = expression.args.get("into") 2600 if not self.SUPPORTS_SELECT_INTO and into: 2601 into.pop() 2602 2603 hint = self.sql(expression, "hint") 2604 distinct = self.sql(expression, "distinct") 2605 distinct = f" {distinct}" if distinct else "" 2606 kind = self.sql(expression, "kind") 2607 2608 limit = expression.args.get("limit") 2609 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2610 top = self.limit_sql(limit, top=True) 2611 limit.pop() 2612 else: 2613 top = "" 2614 2615 expressions = self.expressions(expression) 2616 2617 if kind: 2618 if kind in self.SELECT_KINDS: 2619 kind = f" AS {kind}" 2620 else: 2621 if kind == "STRUCT": 2622 expressions = self.expressions( 2623 sqls=[ 2624 self.sql( 2625 exp.Struct( 2626 expressions=[ 2627 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2628 if isinstance(e, exp.Alias) 2629 else e 2630 for e in expression.expressions 2631 ] 2632 ) 2633 ) 2634 ] 2635 ) 2636 kind = "" 2637 2638 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2639 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2640 2641 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2642 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2643 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2644 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2645 sql = self.query_modifiers( 2646 expression, 2647 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2648 self.sql(expression, "into", comment=False), 2649 self.sql(expression, "from", comment=False), 2650 ) 2651 2652 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2653 if expression.args.get("with"): 2654 sql = self.maybe_comment(sql, expression) 2655 expression.pop_comments() 2656 2657 sql = self.prepend_ctes(expression, sql) 2658 2659 if not self.SUPPORTS_SELECT_INTO and into: 2660 if into.args.get("temporary"): 2661 table_kind = " TEMPORARY" 2662 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2663 table_kind = " UNLOGGED" 2664 else: 2665 table_kind = "" 2666 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2667 2668 return sql 2669 2670 def schema_sql(self, expression: exp.Schema) -> str: 2671 this = self.sql(expression, "this") 2672 sql = self.schema_columns_sql(expression) 2673 return f"{this} {sql}" if this and sql else this or sql 2674 2675 def schema_columns_sql(self, expression: exp.Schema) -> str: 2676 if expression.expressions: 2677 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2678 return "" 2679 2680 def star_sql(self, expression: exp.Star) -> str: 2681 except_ = self.expressions(expression, key="except", flat=True) 2682 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2683 replace = self.expressions(expression, key="replace", flat=True) 2684 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2685 rename = self.expressions(expression, key="rename", flat=True) 2686 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2687 return f"*{except_}{replace}{rename}" 2688 2689 def parameter_sql(self, expression: exp.Parameter) -> str: 2690 this = self.sql(expression, "this") 2691 return f"{self.PARAMETER_TOKEN}{this}" 2692 2693 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2694 this = self.sql(expression, "this") 2695 kind = expression.text("kind") 2696 if kind: 2697 kind = f"{kind}." 2698 return f"@@{kind}{this}" 2699 2700 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2701 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2702 2703 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2704 alias = self.sql(expression, "alias") 2705 alias = f"{sep}{alias}" if alias else "" 2706 sample = self.sql(expression, "sample") 2707 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2708 alias = f"{sample}{alias}" 2709 2710 # Set to None so it's not generated again by self.query_modifiers() 2711 expression.set("sample", None) 2712 2713 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2714 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2715 return self.prepend_ctes(expression, sql) 2716 2717 def qualify_sql(self, expression: exp.Qualify) -> str: 2718 this = self.indent(self.sql(expression, "this")) 2719 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2720 2721 def unnest_sql(self, expression: exp.Unnest) -> str: 2722 args = self.expressions(expression, flat=True) 2723 2724 alias = expression.args.get("alias") 2725 offset = expression.args.get("offset") 2726 2727 if self.UNNEST_WITH_ORDINALITY: 2728 if alias and isinstance(offset, exp.Expression): 2729 alias.append("columns", offset) 2730 2731 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2732 columns = alias.columns 2733 alias = self.sql(columns[0]) if columns else "" 2734 else: 2735 alias = self.sql(alias) 2736 2737 alias = f" AS {alias}" if alias else alias 2738 if self.UNNEST_WITH_ORDINALITY: 2739 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2740 else: 2741 if isinstance(offset, exp.Expression): 2742 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2743 elif offset: 2744 suffix = f"{alias} WITH OFFSET" 2745 else: 2746 suffix = alias 2747 2748 return f"UNNEST({args}){suffix}" 2749 2750 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2751 return "" 2752 2753 def where_sql(self, expression: exp.Where) -> str: 2754 this = self.indent(self.sql(expression, "this")) 2755 return f"{self.seg('WHERE')}{self.sep()}{this}" 2756 2757 def window_sql(self, expression: exp.Window) -> str: 2758 this = self.sql(expression, "this") 2759 partition = self.partition_by_sql(expression) 2760 order = expression.args.get("order") 2761 order = self.order_sql(order, flat=True) if order else "" 2762 spec = self.sql(expression, "spec") 2763 alias = self.sql(expression, "alias") 2764 over = self.sql(expression, "over") or "OVER" 2765 2766 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2767 2768 first = expression.args.get("first") 2769 if first is None: 2770 first = "" 2771 else: 2772 first = "FIRST" if first else "LAST" 2773 2774 if not partition and not order and not spec and alias: 2775 return f"{this} {alias}" 2776 2777 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2778 return f"{this} ({args})" 2779 2780 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2781 partition = self.expressions(expression, key="partition_by", flat=True) 2782 return f"PARTITION BY {partition}" if partition else "" 2783 2784 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2785 kind = self.sql(expression, "kind") 2786 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2787 end = ( 2788 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2789 or "CURRENT ROW" 2790 ) 2791 return f"{kind} BETWEEN {start} AND {end}" 2792 2793 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2794 this = self.sql(expression, "this") 2795 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2796 return f"{this} WITHIN GROUP ({expression_sql})" 2797 2798 def between_sql(self, expression: exp.Between) -> str: 2799 this = self.sql(expression, "this") 2800 low = self.sql(expression, "low") 2801 high = self.sql(expression, "high") 2802 return f"{this} BETWEEN {low} AND {high}" 2803 2804 def bracket_offset_expressions( 2805 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2806 ) -> t.List[exp.Expression]: 2807 return apply_index_offset( 2808 expression.this, 2809 expression.expressions, 2810 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2811 dialect=self.dialect, 2812 ) 2813 2814 def bracket_sql(self, expression: exp.Bracket) -> str: 2815 expressions = self.bracket_offset_expressions(expression) 2816 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2817 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2818 2819 def all_sql(self, expression: exp.All) -> str: 2820 return f"ALL {self.wrap(expression)}" 2821 2822 def any_sql(self, expression: exp.Any) -> str: 2823 this = self.sql(expression, "this") 2824 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2825 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2826 this = self.wrap(this) 2827 return f"ANY{this}" 2828 return f"ANY {this}" 2829 2830 def exists_sql(self, expression: exp.Exists) -> str: 2831 return f"EXISTS{self.wrap(expression)}" 2832 2833 def case_sql(self, expression: exp.Case) -> str: 2834 this = self.sql(expression, "this") 2835 statements = [f"CASE {this}" if this else "CASE"] 2836 2837 for e in expression.args["ifs"]: 2838 statements.append(f"WHEN {self.sql(e, 'this')}") 2839 statements.append(f"THEN {self.sql(e, 'true')}") 2840 2841 default = self.sql(expression, "default") 2842 2843 if default: 2844 statements.append(f"ELSE {default}") 2845 2846 statements.append("END") 2847 2848 if self.pretty and self.too_wide(statements): 2849 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2850 2851 return " ".join(statements) 2852 2853 def constraint_sql(self, expression: exp.Constraint) -> str: 2854 this = self.sql(expression, "this") 2855 expressions = self.expressions(expression, flat=True) 2856 return f"CONSTRAINT {this} {expressions}" 2857 2858 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2859 order = expression.args.get("order") 2860 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2861 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2862 2863 def extract_sql(self, expression: exp.Extract) -> str: 2864 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2865 expression_sql = self.sql(expression, "expression") 2866 return f"EXTRACT({this} FROM {expression_sql})" 2867 2868 def trim_sql(self, expression: exp.Trim) -> str: 2869 trim_type = self.sql(expression, "position") 2870 2871 if trim_type == "LEADING": 2872 func_name = "LTRIM" 2873 elif trim_type == "TRAILING": 2874 func_name = "RTRIM" 2875 else: 2876 func_name = "TRIM" 2877 2878 return self.func(func_name, expression.this, expression.expression) 2879 2880 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2881 args = expression.expressions 2882 if isinstance(expression, exp.ConcatWs): 2883 args = args[1:] # Skip the delimiter 2884 2885 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2886 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2887 2888 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2889 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2890 2891 return args 2892 2893 def concat_sql(self, expression: exp.Concat) -> str: 2894 expressions = self.convert_concat_args(expression) 2895 2896 # Some dialects don't allow a single-argument CONCAT call 2897 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2898 return self.sql(expressions[0]) 2899 2900 return self.func("CONCAT", *expressions) 2901 2902 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2903 return self.func( 2904 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2905 ) 2906 2907 def check_sql(self, expression: exp.Check) -> str: 2908 this = self.sql(expression, key="this") 2909 return f"CHECK ({this})" 2910 2911 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2912 expressions = self.expressions(expression, flat=True) 2913 expressions = f" ({expressions})" if expressions else "" 2914 reference = self.sql(expression, "reference") 2915 reference = f" {reference}" if reference else "" 2916 delete = self.sql(expression, "delete") 2917 delete = f" ON DELETE {delete}" if delete else "" 2918 update = self.sql(expression, "update") 2919 update = f" ON UPDATE {update}" if update else "" 2920 return f"FOREIGN KEY{expressions}{reference}{delete}{update}" 2921 2922 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2923 expressions = self.expressions(expression, flat=True) 2924 options = self.expressions(expression, key="options", flat=True, sep=" ") 2925 options = f" {options}" if options else "" 2926 return f"PRIMARY KEY ({expressions}){options}" 2927 2928 def if_sql(self, expression: exp.If) -> str: 2929 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2930 2931 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2932 modifier = expression.args.get("modifier") 2933 modifier = f" {modifier}" if modifier else "" 2934 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2935 2936 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2937 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2938 2939 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2940 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2941 2942 if expression.args.get("escape"): 2943 path = self.escape_str(path) 2944 2945 if self.QUOTE_JSON_PATH: 2946 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2947 2948 return path 2949 2950 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2951 if isinstance(expression, exp.JSONPathPart): 2952 transform = self.TRANSFORMS.get(expression.__class__) 2953 if not callable(transform): 2954 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2955 return "" 2956 2957 return transform(self, expression) 2958 2959 if isinstance(expression, int): 2960 return str(expression) 2961 2962 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2963 escaped = expression.replace("'", "\\'") 2964 escaped = f"\\'{expression}\\'" 2965 else: 2966 escaped = expression.replace('"', '\\"') 2967 escaped = f'"{escaped}"' 2968 2969 return escaped 2970 2971 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2972 return f"{self.sql(expression, 'this')} FORMAT JSON" 2973 2974 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2975 null_handling = expression.args.get("null_handling") 2976 null_handling = f" {null_handling}" if null_handling else "" 2977 2978 unique_keys = expression.args.get("unique_keys") 2979 if unique_keys is not None: 2980 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2981 else: 2982 unique_keys = "" 2983 2984 return_type = self.sql(expression, "return_type") 2985 return_type = f" RETURNING {return_type}" if return_type else "" 2986 encoding = self.sql(expression, "encoding") 2987 encoding = f" ENCODING {encoding}" if encoding else "" 2988 2989 return self.func( 2990 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2991 *expression.expressions, 2992 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2993 ) 2994 2995 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2996 return self.jsonobject_sql(expression) 2997 2998 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2999 null_handling = expression.args.get("null_handling") 3000 null_handling = f" {null_handling}" if null_handling else "" 3001 return_type = self.sql(expression, "return_type") 3002 return_type = f" RETURNING {return_type}" if return_type else "" 3003 strict = " STRICT" if expression.args.get("strict") else "" 3004 return self.func( 3005 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3006 ) 3007 3008 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3009 this = self.sql(expression, "this") 3010 order = self.sql(expression, "order") 3011 null_handling = expression.args.get("null_handling") 3012 null_handling = f" {null_handling}" if null_handling else "" 3013 return_type = self.sql(expression, "return_type") 3014 return_type = f" RETURNING {return_type}" if return_type else "" 3015 strict = " STRICT" if expression.args.get("strict") else "" 3016 return self.func( 3017 "JSON_ARRAYAGG", 3018 this, 3019 suffix=f"{order}{null_handling}{return_type}{strict})", 3020 ) 3021 3022 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3023 path = self.sql(expression, "path") 3024 path = f" PATH {path}" if path else "" 3025 nested_schema = self.sql(expression, "nested_schema") 3026 3027 if nested_schema: 3028 return f"NESTED{path} {nested_schema}" 3029 3030 this = self.sql(expression, "this") 3031 kind = self.sql(expression, "kind") 3032 kind = f" {kind}" if kind else "" 3033 return f"{this}{kind}{path}" 3034 3035 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3036 return self.func("COLUMNS", *expression.expressions) 3037 3038 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3039 this = self.sql(expression, "this") 3040 path = self.sql(expression, "path") 3041 path = f", {path}" if path else "" 3042 error_handling = expression.args.get("error_handling") 3043 error_handling = f" {error_handling}" if error_handling else "" 3044 empty_handling = expression.args.get("empty_handling") 3045 empty_handling = f" {empty_handling}" if empty_handling else "" 3046 schema = self.sql(expression, "schema") 3047 return self.func( 3048 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3049 ) 3050 3051 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3052 this = self.sql(expression, "this") 3053 kind = self.sql(expression, "kind") 3054 path = self.sql(expression, "path") 3055 path = f" {path}" if path else "" 3056 as_json = " AS JSON" if expression.args.get("as_json") else "" 3057 return f"{this} {kind}{path}{as_json}" 3058 3059 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3060 this = self.sql(expression, "this") 3061 path = self.sql(expression, "path") 3062 path = f", {path}" if path else "" 3063 expressions = self.expressions(expression) 3064 with_ = ( 3065 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3066 if expressions 3067 else "" 3068 ) 3069 return f"OPENJSON({this}{path}){with_}" 3070 3071 def in_sql(self, expression: exp.In) -> str: 3072 query = expression.args.get("query") 3073 unnest = expression.args.get("unnest") 3074 field = expression.args.get("field") 3075 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3076 3077 if query: 3078 in_sql = self.sql(query) 3079 elif unnest: 3080 in_sql = self.in_unnest_op(unnest) 3081 elif field: 3082 in_sql = self.sql(field) 3083 else: 3084 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3085 3086 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3087 3088 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3089 return f"(SELECT {self.sql(unnest)})" 3090 3091 def interval_sql(self, expression: exp.Interval) -> str: 3092 unit = self.sql(expression, "unit") 3093 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3094 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3095 unit = f" {unit}" if unit else "" 3096 3097 if self.SINGLE_STRING_INTERVAL: 3098 this = expression.this.name if expression.this else "" 3099 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3100 3101 this = self.sql(expression, "this") 3102 if this: 3103 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3104 this = f" {this}" if unwrapped else f" ({this})" 3105 3106 return f"INTERVAL{this}{unit}" 3107 3108 def return_sql(self, expression: exp.Return) -> str: 3109 return f"RETURN {self.sql(expression, 'this')}" 3110 3111 def reference_sql(self, expression: exp.Reference) -> str: 3112 this = self.sql(expression, "this") 3113 expressions = self.expressions(expression, flat=True) 3114 expressions = f"({expressions})" if expressions else "" 3115 options = self.expressions(expression, key="options", flat=True, sep=" ") 3116 options = f" {options}" if options else "" 3117 return f"REFERENCES {this}{expressions}{options}" 3118 3119 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3120 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3121 parent = expression.parent 3122 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3123 return self.func( 3124 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3125 ) 3126 3127 def paren_sql(self, expression: exp.Paren) -> str: 3128 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3129 return f"({sql}{self.seg(')', sep='')}" 3130 3131 def neg_sql(self, expression: exp.Neg) -> str: 3132 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3133 this_sql = self.sql(expression, "this") 3134 sep = " " if this_sql[0] == "-" else "" 3135 return f"-{sep}{this_sql}" 3136 3137 def not_sql(self, expression: exp.Not) -> str: 3138 return f"NOT {self.sql(expression, 'this')}" 3139 3140 def alias_sql(self, expression: exp.Alias) -> str: 3141 alias = self.sql(expression, "alias") 3142 alias = f" AS {alias}" if alias else "" 3143 return f"{self.sql(expression, 'this')}{alias}" 3144 3145 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3146 alias = expression.args["alias"] 3147 3148 parent = expression.parent 3149 pivot = parent and parent.parent 3150 3151 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3152 identifier_alias = isinstance(alias, exp.Identifier) 3153 literal_alias = isinstance(alias, exp.Literal) 3154 3155 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3156 alias.replace(exp.Literal.string(alias.output_name)) 3157 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3158 alias.replace(exp.to_identifier(alias.output_name)) 3159 3160 return self.alias_sql(expression) 3161 3162 def aliases_sql(self, expression: exp.Aliases) -> str: 3163 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3164 3165 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3166 this = self.sql(expression, "this") 3167 index = self.sql(expression, "expression") 3168 return f"{this} AT {index}" 3169 3170 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3171 this = self.sql(expression, "this") 3172 zone = self.sql(expression, "zone") 3173 return f"{this} AT TIME ZONE {zone}" 3174 3175 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3176 this = self.sql(expression, "this") 3177 zone = self.sql(expression, "zone") 3178 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3179 3180 def add_sql(self, expression: exp.Add) -> str: 3181 return self.binary(expression, "+") 3182 3183 def and_sql( 3184 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3185 ) -> str: 3186 return self.connector_sql(expression, "AND", stack) 3187 3188 def or_sql( 3189 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3190 ) -> str: 3191 return self.connector_sql(expression, "OR", stack) 3192 3193 def xor_sql( 3194 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3195 ) -> str: 3196 return self.connector_sql(expression, "XOR", stack) 3197 3198 def connector_sql( 3199 self, 3200 expression: exp.Connector, 3201 op: str, 3202 stack: t.Optional[t.List[str | exp.Expression]] = None, 3203 ) -> str: 3204 if stack is not None: 3205 if expression.expressions: 3206 stack.append(self.expressions(expression, sep=f" {op} ")) 3207 else: 3208 stack.append(expression.right) 3209 if expression.comments and self.comments: 3210 for comment in expression.comments: 3211 if comment: 3212 op += f" /*{self.pad_comment(comment)}*/" 3213 stack.extend((op, expression.left)) 3214 return op 3215 3216 stack = [expression] 3217 sqls: t.List[str] = [] 3218 ops = set() 3219 3220 while stack: 3221 node = stack.pop() 3222 if isinstance(node, exp.Connector): 3223 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3224 else: 3225 sql = self.sql(node) 3226 if sqls and sqls[-1] in ops: 3227 sqls[-1] += f" {sql}" 3228 else: 3229 sqls.append(sql) 3230 3231 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3232 return sep.join(sqls) 3233 3234 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3235 return self.binary(expression, "&") 3236 3237 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3238 return self.binary(expression, "<<") 3239 3240 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3241 return f"~{self.sql(expression, 'this')}" 3242 3243 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3244 return self.binary(expression, "|") 3245 3246 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3247 return self.binary(expression, ">>") 3248 3249 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3250 return self.binary(expression, "^") 3251 3252 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3253 format_sql = self.sql(expression, "format") 3254 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3255 to_sql = self.sql(expression, "to") 3256 to_sql = f" {to_sql}" if to_sql else "" 3257 action = self.sql(expression, "action") 3258 action = f" {action}" if action else "" 3259 default = self.sql(expression, "default") 3260 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3261 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3262 3263 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3264 zone = self.sql(expression, "this") 3265 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3266 3267 def collate_sql(self, expression: exp.Collate) -> str: 3268 if self.COLLATE_IS_FUNC: 3269 return self.function_fallback_sql(expression) 3270 return self.binary(expression, "COLLATE") 3271 3272 def command_sql(self, expression: exp.Command) -> str: 3273 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3274 3275 def comment_sql(self, expression: exp.Comment) -> str: 3276 this = self.sql(expression, "this") 3277 kind = expression.args["kind"] 3278 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3279 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3280 expression_sql = self.sql(expression, "expression") 3281 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3282 3283 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3284 this = self.sql(expression, "this") 3285 delete = " DELETE" if expression.args.get("delete") else "" 3286 recompress = self.sql(expression, "recompress") 3287 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3288 to_disk = self.sql(expression, "to_disk") 3289 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3290 to_volume = self.sql(expression, "to_volume") 3291 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3292 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3293 3294 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3295 where = self.sql(expression, "where") 3296 group = self.sql(expression, "group") 3297 aggregates = self.expressions(expression, key="aggregates") 3298 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3299 3300 if not (where or group or aggregates) and len(expression.expressions) == 1: 3301 return f"TTL {self.expressions(expression, flat=True)}" 3302 3303 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3304 3305 def transaction_sql(self, expression: exp.Transaction) -> str: 3306 return "BEGIN" 3307 3308 def commit_sql(self, expression: exp.Commit) -> str: 3309 chain = expression.args.get("chain") 3310 if chain is not None: 3311 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3312 3313 return f"COMMIT{chain or ''}" 3314 3315 def rollback_sql(self, expression: exp.Rollback) -> str: 3316 savepoint = expression.args.get("savepoint") 3317 savepoint = f" TO {savepoint}" if savepoint else "" 3318 return f"ROLLBACK{savepoint}" 3319 3320 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3321 this = self.sql(expression, "this") 3322 3323 dtype = self.sql(expression, "dtype") 3324 if dtype: 3325 collate = self.sql(expression, "collate") 3326 collate = f" COLLATE {collate}" if collate else "" 3327 using = self.sql(expression, "using") 3328 using = f" USING {using}" if using else "" 3329 return f"ALTER COLUMN {this} {self.ALTER_SET_TYPE} {dtype}{collate}{using}" 3330 3331 default = self.sql(expression, "default") 3332 if default: 3333 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3334 3335 comment = self.sql(expression, "comment") 3336 if comment: 3337 return f"ALTER COLUMN {this} COMMENT {comment}" 3338 3339 visible = expression.args.get("visible") 3340 if visible: 3341 return f"ALTER COLUMN {this} SET {visible}" 3342 3343 allow_null = expression.args.get("allow_null") 3344 drop = expression.args.get("drop") 3345 3346 if not drop and not allow_null: 3347 self.unsupported("Unsupported ALTER COLUMN syntax") 3348 3349 if allow_null is not None: 3350 keyword = "DROP" if drop else "SET" 3351 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3352 3353 return f"ALTER COLUMN {this} DROP DEFAULT" 3354 3355 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3356 this = self.sql(expression, "this") 3357 3358 visible = expression.args.get("visible") 3359 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3360 3361 return f"ALTER INDEX {this} {visible_sql}" 3362 3363 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3364 this = self.sql(expression, "this") 3365 if not isinstance(expression.this, exp.Var): 3366 this = f"KEY DISTKEY {this}" 3367 return f"ALTER DISTSTYLE {this}" 3368 3369 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3370 compound = " COMPOUND" if expression.args.get("compound") else "" 3371 this = self.sql(expression, "this") 3372 expressions = self.expressions(expression, flat=True) 3373 expressions = f"({expressions})" if expressions else "" 3374 return f"ALTER{compound} SORTKEY {this or expressions}" 3375 3376 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3377 if not self.RENAME_TABLE_WITH_DB: 3378 # Remove db from tables 3379 expression = expression.transform( 3380 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3381 ).assert_is(exp.AlterRename) 3382 this = self.sql(expression, "this") 3383 return f"RENAME TO {this}" 3384 3385 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3386 exists = " IF EXISTS" if expression.args.get("exists") else "" 3387 old_column = self.sql(expression, "this") 3388 new_column = self.sql(expression, "to") 3389 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3390 3391 def alterset_sql(self, expression: exp.AlterSet) -> str: 3392 exprs = self.expressions(expression, flat=True) 3393 return f"SET {exprs}" 3394 3395 def alter_sql(self, expression: exp.Alter) -> str: 3396 actions = expression.args["actions"] 3397 3398 if isinstance(actions[0], exp.ColumnDef): 3399 actions = self.add_column_sql(expression) 3400 elif isinstance(actions[0], exp.Schema): 3401 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3402 elif isinstance(actions[0], exp.Delete): 3403 actions = self.expressions(expression, key="actions", flat=True) 3404 elif isinstance(actions[0], exp.Query): 3405 actions = "AS " + self.expressions(expression, key="actions") 3406 else: 3407 actions = self.expressions(expression, key="actions", flat=True) 3408 3409 exists = " IF EXISTS" if expression.args.get("exists") else "" 3410 on_cluster = self.sql(expression, "cluster") 3411 on_cluster = f" {on_cluster}" if on_cluster else "" 3412 only = " ONLY" if expression.args.get("only") else "" 3413 options = self.expressions(expression, key="options") 3414 options = f", {options}" if options else "" 3415 kind = self.sql(expression, "kind") 3416 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3417 3418 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3419 3420 def add_column_sql(self, expression: exp.Alter) -> str: 3421 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3422 return self.expressions( 3423 expression, 3424 key="actions", 3425 prefix="ADD COLUMN ", 3426 skip_first=True, 3427 ) 3428 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3429 3430 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3431 expressions = self.expressions(expression) 3432 exists = " IF EXISTS " if expression.args.get("exists") else " " 3433 return f"DROP{exists}{expressions}" 3434 3435 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3436 return f"ADD {self.expressions(expression)}" 3437 3438 def distinct_sql(self, expression: exp.Distinct) -> str: 3439 this = self.expressions(expression, flat=True) 3440 3441 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3442 case = exp.case() 3443 for arg in expression.expressions: 3444 case = case.when(arg.is_(exp.null()), exp.null()) 3445 this = self.sql(case.else_(f"({this})")) 3446 3447 this = f" {this}" if this else "" 3448 3449 on = self.sql(expression, "on") 3450 on = f" ON {on}" if on else "" 3451 return f"DISTINCT{this}{on}" 3452 3453 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3454 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3455 3456 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3457 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3458 3459 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3460 this_sql = self.sql(expression, "this") 3461 expression_sql = self.sql(expression, "expression") 3462 kind = "MAX" if expression.args.get("max") else "MIN" 3463 return f"{this_sql} HAVING {kind} {expression_sql}" 3464 3465 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3466 return self.sql( 3467 exp.Cast( 3468 this=exp.Div(this=expression.this, expression=expression.expression), 3469 to=exp.DataType(this=exp.DataType.Type.INT), 3470 ) 3471 ) 3472 3473 def dpipe_sql(self, expression: exp.DPipe) -> str: 3474 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3475 return self.func( 3476 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3477 ) 3478 return self.binary(expression, "||") 3479 3480 def div_sql(self, expression: exp.Div) -> str: 3481 l, r = expression.left, expression.right 3482 3483 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3484 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3485 3486 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3487 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3488 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3489 3490 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3491 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3492 return self.sql( 3493 exp.cast( 3494 l / r, 3495 to=exp.DataType.Type.BIGINT, 3496 ) 3497 ) 3498 3499 return self.binary(expression, "/") 3500 3501 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3502 n = exp._wrap(expression.this, exp.Binary) 3503 d = exp._wrap(expression.expression, exp.Binary) 3504 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3505 3506 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3507 return self.binary(expression, "OVERLAPS") 3508 3509 def distance_sql(self, expression: exp.Distance) -> str: 3510 return self.binary(expression, "<->") 3511 3512 def dot_sql(self, expression: exp.Dot) -> str: 3513 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3514 3515 def eq_sql(self, expression: exp.EQ) -> str: 3516 return self.binary(expression, "=") 3517 3518 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3519 return self.binary(expression, ":=") 3520 3521 def escape_sql(self, expression: exp.Escape) -> str: 3522 return self.binary(expression, "ESCAPE") 3523 3524 def glob_sql(self, expression: exp.Glob) -> str: 3525 return self.binary(expression, "GLOB") 3526 3527 def gt_sql(self, expression: exp.GT) -> str: 3528 return self.binary(expression, ">") 3529 3530 def gte_sql(self, expression: exp.GTE) -> str: 3531 return self.binary(expression, ">=") 3532 3533 def ilike_sql(self, expression: exp.ILike) -> str: 3534 return self.binary(expression, "ILIKE") 3535 3536 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3537 return self.binary(expression, "ILIKE ANY") 3538 3539 def is_sql(self, expression: exp.Is) -> str: 3540 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3541 return self.sql( 3542 expression.this if expression.expression.this else exp.not_(expression.this) 3543 ) 3544 return self.binary(expression, "IS") 3545 3546 def like_sql(self, expression: exp.Like) -> str: 3547 return self.binary(expression, "LIKE") 3548 3549 def likeany_sql(self, expression: exp.LikeAny) -> str: 3550 return self.binary(expression, "LIKE ANY") 3551 3552 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3553 return self.binary(expression, "SIMILAR TO") 3554 3555 def lt_sql(self, expression: exp.LT) -> str: 3556 return self.binary(expression, "<") 3557 3558 def lte_sql(self, expression: exp.LTE) -> str: 3559 return self.binary(expression, "<=") 3560 3561 def mod_sql(self, expression: exp.Mod) -> str: 3562 return self.binary(expression, "%") 3563 3564 def mul_sql(self, expression: exp.Mul) -> str: 3565 return self.binary(expression, "*") 3566 3567 def neq_sql(self, expression: exp.NEQ) -> str: 3568 return self.binary(expression, "<>") 3569 3570 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3571 return self.binary(expression, "IS NOT DISTINCT FROM") 3572 3573 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3574 return self.binary(expression, "IS DISTINCT FROM") 3575 3576 def slice_sql(self, expression: exp.Slice) -> str: 3577 return self.binary(expression, ":") 3578 3579 def sub_sql(self, expression: exp.Sub) -> str: 3580 return self.binary(expression, "-") 3581 3582 def trycast_sql(self, expression: exp.TryCast) -> str: 3583 return self.cast_sql(expression, safe_prefix="TRY_") 3584 3585 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3586 return self.cast_sql(expression) 3587 3588 def try_sql(self, expression: exp.Try) -> str: 3589 if not self.TRY_SUPPORTED: 3590 self.unsupported("Unsupported TRY function") 3591 return self.sql(expression, "this") 3592 3593 return self.func("TRY", expression.this) 3594 3595 def log_sql(self, expression: exp.Log) -> str: 3596 this = expression.this 3597 expr = expression.expression 3598 3599 if self.dialect.LOG_BASE_FIRST is False: 3600 this, expr = expr, this 3601 elif self.dialect.LOG_BASE_FIRST is None and expr: 3602 if this.name in ("2", "10"): 3603 return self.func(f"LOG{this.name}", expr) 3604 3605 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3606 3607 return self.func("LOG", this, expr) 3608 3609 def use_sql(self, expression: exp.Use) -> str: 3610 kind = self.sql(expression, "kind") 3611 kind = f" {kind}" if kind else "" 3612 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3613 this = f" {this}" if this else "" 3614 return f"USE{kind}{this}" 3615 3616 def binary(self, expression: exp.Binary, op: str) -> str: 3617 sqls: t.List[str] = [] 3618 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3619 binary_type = type(expression) 3620 3621 while stack: 3622 node = stack.pop() 3623 3624 if type(node) is binary_type: 3625 op_func = node.args.get("operator") 3626 if op_func: 3627 op = f"OPERATOR({self.sql(op_func)})" 3628 3629 stack.append(node.right) 3630 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3631 stack.append(node.left) 3632 else: 3633 sqls.append(self.sql(node)) 3634 3635 return "".join(sqls) 3636 3637 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3638 to_clause = self.sql(expression, "to") 3639 if to_clause: 3640 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3641 3642 return self.function_fallback_sql(expression) 3643 3644 def function_fallback_sql(self, expression: exp.Func) -> str: 3645 args = [] 3646 3647 for key in expression.arg_types: 3648 arg_value = expression.args.get(key) 3649 3650 if isinstance(arg_value, list): 3651 for value in arg_value: 3652 args.append(value) 3653 elif arg_value is not None: 3654 args.append(arg_value) 3655 3656 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3657 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3658 else: 3659 name = expression.sql_name() 3660 3661 return self.func(name, *args) 3662 3663 def func( 3664 self, 3665 name: str, 3666 *args: t.Optional[exp.Expression | str], 3667 prefix: str = "(", 3668 suffix: str = ")", 3669 normalize: bool = True, 3670 ) -> str: 3671 name = self.normalize_func(name) if normalize else name 3672 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3673 3674 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3675 arg_sqls = tuple( 3676 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3677 ) 3678 if self.pretty and self.too_wide(arg_sqls): 3679 return self.indent( 3680 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3681 ) 3682 return sep.join(arg_sqls) 3683 3684 def too_wide(self, args: t.Iterable) -> bool: 3685 return sum(len(arg) for arg in args) > self.max_text_width 3686 3687 def format_time( 3688 self, 3689 expression: exp.Expression, 3690 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3691 inverse_time_trie: t.Optional[t.Dict] = None, 3692 ) -> t.Optional[str]: 3693 return format_time( 3694 self.sql(expression, "format"), 3695 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3696 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3697 ) 3698 3699 def expressions( 3700 self, 3701 expression: t.Optional[exp.Expression] = None, 3702 key: t.Optional[str] = None, 3703 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3704 flat: bool = False, 3705 indent: bool = True, 3706 skip_first: bool = False, 3707 skip_last: bool = False, 3708 sep: str = ", ", 3709 prefix: str = "", 3710 dynamic: bool = False, 3711 new_line: bool = False, 3712 ) -> str: 3713 expressions = expression.args.get(key or "expressions") if expression else sqls 3714 3715 if not expressions: 3716 return "" 3717 3718 if flat: 3719 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3720 3721 num_sqls = len(expressions) 3722 result_sqls = [] 3723 3724 for i, e in enumerate(expressions): 3725 sql = self.sql(e, comment=False) 3726 if not sql: 3727 continue 3728 3729 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3730 3731 if self.pretty: 3732 if self.leading_comma: 3733 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3734 else: 3735 result_sqls.append( 3736 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3737 ) 3738 else: 3739 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3740 3741 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3742 if new_line: 3743 result_sqls.insert(0, "") 3744 result_sqls.append("") 3745 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3746 else: 3747 result_sql = "".join(result_sqls) 3748 3749 return ( 3750 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3751 if indent 3752 else result_sql 3753 ) 3754 3755 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3756 flat = flat or isinstance(expression.parent, exp.Properties) 3757 expressions_sql = self.expressions(expression, flat=flat) 3758 if flat: 3759 return f"{op} {expressions_sql}" 3760 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3761 3762 def naked_property(self, expression: exp.Property) -> str: 3763 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3764 if not property_name: 3765 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3766 return f"{property_name} {self.sql(expression, 'this')}" 3767 3768 def tag_sql(self, expression: exp.Tag) -> str: 3769 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3770 3771 def token_sql(self, token_type: TokenType) -> str: 3772 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3773 3774 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3775 this = self.sql(expression, "this") 3776 expressions = self.no_identify(self.expressions, expression) 3777 expressions = ( 3778 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3779 ) 3780 return f"{this}{expressions}" if expressions.strip() != "" else this 3781 3782 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3783 this = self.sql(expression, "this") 3784 expressions = self.expressions(expression, flat=True) 3785 return f"{this}({expressions})" 3786 3787 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3788 return self.binary(expression, "=>") 3789 3790 def when_sql(self, expression: exp.When) -> str: 3791 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3792 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3793 condition = self.sql(expression, "condition") 3794 condition = f" AND {condition}" if condition else "" 3795 3796 then_expression = expression.args.get("then") 3797 if isinstance(then_expression, exp.Insert): 3798 this = self.sql(then_expression, "this") 3799 this = f"INSERT {this}" if this else "INSERT" 3800 then = self.sql(then_expression, "expression") 3801 then = f"{this} VALUES {then}" if then else this 3802 elif isinstance(then_expression, exp.Update): 3803 if isinstance(then_expression.args.get("expressions"), exp.Star): 3804 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3805 else: 3806 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3807 else: 3808 then = self.sql(then_expression) 3809 return f"WHEN {matched}{source}{condition} THEN {then}" 3810 3811 def whens_sql(self, expression: exp.Whens) -> str: 3812 return self.expressions(expression, sep=" ", indent=False) 3813 3814 def merge_sql(self, expression: exp.Merge) -> str: 3815 table = expression.this 3816 table_alias = "" 3817 3818 hints = table.args.get("hints") 3819 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3820 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3821 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3822 3823 this = self.sql(table) 3824 using = f"USING {self.sql(expression, 'using')}" 3825 on = f"ON {self.sql(expression, 'on')}" 3826 whens = self.sql(expression, "whens") 3827 3828 returning = self.sql(expression, "returning") 3829 if returning: 3830 whens = f"{whens}{returning}" 3831 3832 sep = self.sep() 3833 3834 return self.prepend_ctes( 3835 expression, 3836 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3837 ) 3838 3839 @unsupported_args("format") 3840 def tochar_sql(self, expression: exp.ToChar) -> str: 3841 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3842 3843 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3844 if not self.SUPPORTS_TO_NUMBER: 3845 self.unsupported("Unsupported TO_NUMBER function") 3846 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3847 3848 fmt = expression.args.get("format") 3849 if not fmt: 3850 self.unsupported("Conversion format is required for TO_NUMBER") 3851 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3852 3853 return self.func("TO_NUMBER", expression.this, fmt) 3854 3855 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3856 this = self.sql(expression, "this") 3857 kind = self.sql(expression, "kind") 3858 settings_sql = self.expressions(expression, key="settings", sep=" ") 3859 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3860 return f"{this}({kind}{args})" 3861 3862 def dictrange_sql(self, expression: exp.DictRange) -> str: 3863 this = self.sql(expression, "this") 3864 max = self.sql(expression, "max") 3865 min = self.sql(expression, "min") 3866 return f"{this}(MIN {min} MAX {max})" 3867 3868 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3869 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3870 3871 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3872 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3873 3874 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3875 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3876 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3877 3878 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3879 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3880 expressions = self.expressions(expression, flat=True) 3881 expressions = f" {self.wrap(expressions)}" if expressions else "" 3882 buckets = self.sql(expression, "buckets") 3883 kind = self.sql(expression, "kind") 3884 buckets = f" BUCKETS {buckets}" if buckets else "" 3885 order = self.sql(expression, "order") 3886 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3887 3888 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3889 return "" 3890 3891 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3892 expressions = self.expressions(expression, key="expressions", flat=True) 3893 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3894 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3895 buckets = self.sql(expression, "buckets") 3896 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3897 3898 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3899 this = self.sql(expression, "this") 3900 having = self.sql(expression, "having") 3901 3902 if having: 3903 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3904 3905 return self.func("ANY_VALUE", this) 3906 3907 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3908 transform = self.func("TRANSFORM", *expression.expressions) 3909 row_format_before = self.sql(expression, "row_format_before") 3910 row_format_before = f" {row_format_before}" if row_format_before else "" 3911 record_writer = self.sql(expression, "record_writer") 3912 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3913 using = f" USING {self.sql(expression, 'command_script')}" 3914 schema = self.sql(expression, "schema") 3915 schema = f" AS {schema}" if schema else "" 3916 row_format_after = self.sql(expression, "row_format_after") 3917 row_format_after = f" {row_format_after}" if row_format_after else "" 3918 record_reader = self.sql(expression, "record_reader") 3919 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3920 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3921 3922 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3923 key_block_size = self.sql(expression, "key_block_size") 3924 if key_block_size: 3925 return f"KEY_BLOCK_SIZE = {key_block_size}" 3926 3927 using = self.sql(expression, "using") 3928 if using: 3929 return f"USING {using}" 3930 3931 parser = self.sql(expression, "parser") 3932 if parser: 3933 return f"WITH PARSER {parser}" 3934 3935 comment = self.sql(expression, "comment") 3936 if comment: 3937 return f"COMMENT {comment}" 3938 3939 visible = expression.args.get("visible") 3940 if visible is not None: 3941 return "VISIBLE" if visible else "INVISIBLE" 3942 3943 engine_attr = self.sql(expression, "engine_attr") 3944 if engine_attr: 3945 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3946 3947 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3948 if secondary_engine_attr: 3949 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3950 3951 self.unsupported("Unsupported index constraint option.") 3952 return "" 3953 3954 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3955 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3956 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3957 3958 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3959 kind = self.sql(expression, "kind") 3960 kind = f"{kind} INDEX" if kind else "INDEX" 3961 this = self.sql(expression, "this") 3962 this = f" {this}" if this else "" 3963 index_type = self.sql(expression, "index_type") 3964 index_type = f" USING {index_type}" if index_type else "" 3965 expressions = self.expressions(expression, flat=True) 3966 expressions = f" ({expressions})" if expressions else "" 3967 options = self.expressions(expression, key="options", sep=" ") 3968 options = f" {options}" if options else "" 3969 return f"{kind}{this}{index_type}{expressions}{options}" 3970 3971 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3972 if self.NVL2_SUPPORTED: 3973 return self.function_fallback_sql(expression) 3974 3975 case = exp.Case().when( 3976 expression.this.is_(exp.null()).not_(copy=False), 3977 expression.args["true"], 3978 copy=False, 3979 ) 3980 else_cond = expression.args.get("false") 3981 if else_cond: 3982 case.else_(else_cond, copy=False) 3983 3984 return self.sql(case) 3985 3986 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3987 this = self.sql(expression, "this") 3988 expr = self.sql(expression, "expression") 3989 iterator = self.sql(expression, "iterator") 3990 condition = self.sql(expression, "condition") 3991 condition = f" IF {condition}" if condition else "" 3992 return f"{this} FOR {expr} IN {iterator}{condition}" 3993 3994 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3995 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3996 3997 def opclass_sql(self, expression: exp.Opclass) -> str: 3998 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3999 4000 def predict_sql(self, expression: exp.Predict) -> str: 4001 model = self.sql(expression, "this") 4002 model = f"MODEL {model}" 4003 table = self.sql(expression, "expression") 4004 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4005 parameters = self.sql(expression, "params_struct") 4006 return self.func("PREDICT", model, table, parameters or None) 4007 4008 def forin_sql(self, expression: exp.ForIn) -> str: 4009 this = self.sql(expression, "this") 4010 expression_sql = self.sql(expression, "expression") 4011 return f"FOR {this} DO {expression_sql}" 4012 4013 def refresh_sql(self, expression: exp.Refresh) -> str: 4014 this = self.sql(expression, "this") 4015 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4016 return f"REFRESH {table}{this}" 4017 4018 def toarray_sql(self, expression: exp.ToArray) -> str: 4019 arg = expression.this 4020 if not arg.type: 4021 from sqlglot.optimizer.annotate_types import annotate_types 4022 4023 arg = annotate_types(arg, dialect=self.dialect) 4024 4025 if arg.is_type(exp.DataType.Type.ARRAY): 4026 return self.sql(arg) 4027 4028 cond_for_null = arg.is_(exp.null()) 4029 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4030 4031 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4032 this = expression.this 4033 time_format = self.format_time(expression) 4034 4035 if time_format: 4036 return self.sql( 4037 exp.cast( 4038 exp.StrToTime(this=this, format=expression.args["format"]), 4039 exp.DataType.Type.TIME, 4040 ) 4041 ) 4042 4043 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4044 return self.sql(this) 4045 4046 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4047 4048 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4049 this = expression.this 4050 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4051 return self.sql(this) 4052 4053 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4054 4055 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4056 this = expression.this 4057 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4058 return self.sql(this) 4059 4060 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4061 4062 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4063 this = expression.this 4064 time_format = self.format_time(expression) 4065 4066 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4067 return self.sql( 4068 exp.cast( 4069 exp.StrToTime(this=this, format=expression.args["format"]), 4070 exp.DataType.Type.DATE, 4071 ) 4072 ) 4073 4074 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4075 return self.sql(this) 4076 4077 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4078 4079 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4080 return self.sql( 4081 exp.func( 4082 "DATEDIFF", 4083 expression.this, 4084 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4085 "day", 4086 ) 4087 ) 4088 4089 def lastday_sql(self, expression: exp.LastDay) -> str: 4090 if self.LAST_DAY_SUPPORTS_DATE_PART: 4091 return self.function_fallback_sql(expression) 4092 4093 unit = expression.text("unit") 4094 if unit and unit != "MONTH": 4095 self.unsupported("Date parts are not supported in LAST_DAY.") 4096 4097 return self.func("LAST_DAY", expression.this) 4098 4099 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4100 from sqlglot.dialects.dialect import unit_to_str 4101 4102 return self.func( 4103 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4104 ) 4105 4106 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4107 if self.CAN_IMPLEMENT_ARRAY_ANY: 4108 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4109 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4110 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4111 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4112 4113 from sqlglot.dialects import Dialect 4114 4115 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4116 if self.dialect.__class__ != Dialect: 4117 self.unsupported("ARRAY_ANY is unsupported") 4118 4119 return self.function_fallback_sql(expression) 4120 4121 def struct_sql(self, expression: exp.Struct) -> str: 4122 expression.set( 4123 "expressions", 4124 [ 4125 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4126 if isinstance(e, exp.PropertyEQ) 4127 else e 4128 for e in expression.expressions 4129 ], 4130 ) 4131 4132 return self.function_fallback_sql(expression) 4133 4134 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4135 low = self.sql(expression, "this") 4136 high = self.sql(expression, "expression") 4137 4138 return f"{low} TO {high}" 4139 4140 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4141 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4142 tables = f" {self.expressions(expression)}" 4143 4144 exists = " IF EXISTS" if expression.args.get("exists") else "" 4145 4146 on_cluster = self.sql(expression, "cluster") 4147 on_cluster = f" {on_cluster}" if on_cluster else "" 4148 4149 identity = self.sql(expression, "identity") 4150 identity = f" {identity} IDENTITY" if identity else "" 4151 4152 option = self.sql(expression, "option") 4153 option = f" {option}" if option else "" 4154 4155 partition = self.sql(expression, "partition") 4156 partition = f" {partition}" if partition else "" 4157 4158 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4159 4160 # This transpiles T-SQL's CONVERT function 4161 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4162 def convert_sql(self, expression: exp.Convert) -> str: 4163 to = expression.this 4164 value = expression.expression 4165 style = expression.args.get("style") 4166 safe = expression.args.get("safe") 4167 strict = expression.args.get("strict") 4168 4169 if not to or not value: 4170 return "" 4171 4172 # Retrieve length of datatype and override to default if not specified 4173 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4174 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4175 4176 transformed: t.Optional[exp.Expression] = None 4177 cast = exp.Cast if strict else exp.TryCast 4178 4179 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4180 if isinstance(style, exp.Literal) and style.is_int: 4181 from sqlglot.dialects.tsql import TSQL 4182 4183 style_value = style.name 4184 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4185 if not converted_style: 4186 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4187 4188 fmt = exp.Literal.string(converted_style) 4189 4190 if to.this == exp.DataType.Type.DATE: 4191 transformed = exp.StrToDate(this=value, format=fmt) 4192 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4193 transformed = exp.StrToTime(this=value, format=fmt) 4194 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4195 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4196 elif to.this == exp.DataType.Type.TEXT: 4197 transformed = exp.TimeToStr(this=value, format=fmt) 4198 4199 if not transformed: 4200 transformed = cast(this=value, to=to, safe=safe) 4201 4202 return self.sql(transformed) 4203 4204 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4205 this = expression.this 4206 if isinstance(this, exp.JSONPathWildcard): 4207 this = self.json_path_part(this) 4208 return f".{this}" if this else "" 4209 4210 if exp.SAFE_IDENTIFIER_RE.match(this): 4211 return f".{this}" 4212 4213 this = self.json_path_part(this) 4214 return ( 4215 f"[{this}]" 4216 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4217 else f".{this}" 4218 ) 4219 4220 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4221 this = self.json_path_part(expression.this) 4222 return f"[{this}]" if this else "" 4223 4224 def _simplify_unless_literal(self, expression: E) -> E: 4225 if not isinstance(expression, exp.Literal): 4226 from sqlglot.optimizer.simplify import simplify 4227 4228 expression = simplify(expression, dialect=self.dialect) 4229 4230 return expression 4231 4232 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4233 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4234 # The first modifier here will be the one closest to the AggFunc's arg 4235 mods = sorted( 4236 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4237 key=lambda x: 0 4238 if isinstance(x, exp.HavingMax) 4239 else (1 if isinstance(x, exp.Order) else 2), 4240 ) 4241 4242 if mods: 4243 mod = mods[0] 4244 this = expression.__class__(this=mod.this.copy()) 4245 this.meta["inline"] = True 4246 mod.this.replace(this) 4247 return self.sql(expression.this) 4248 4249 agg_func = expression.find(exp.AggFunc) 4250 4251 if agg_func: 4252 return self.sql(agg_func)[:-1] + f" {text})" 4253 4254 return f"{self.sql(expression, 'this')} {text}" 4255 4256 def _replace_line_breaks(self, string: str) -> str: 4257 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4258 if self.pretty: 4259 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4260 return string 4261 4262 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4263 option = self.sql(expression, "this") 4264 4265 if expression.expressions: 4266 upper = option.upper() 4267 4268 # Snowflake FILE_FORMAT options are separated by whitespace 4269 sep = " " if upper == "FILE_FORMAT" else ", " 4270 4271 # Databricks copy/format options do not set their list of values with EQ 4272 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4273 values = self.expressions(expression, flat=True, sep=sep) 4274 return f"{option}{op}({values})" 4275 4276 value = self.sql(expression, "expression") 4277 4278 if not value: 4279 return option 4280 4281 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4282 4283 return f"{option}{op}{value}" 4284 4285 def credentials_sql(self, expression: exp.Credentials) -> str: 4286 cred_expr = expression.args.get("credentials") 4287 if isinstance(cred_expr, exp.Literal): 4288 # Redshift case: CREDENTIALS <string> 4289 credentials = self.sql(expression, "credentials") 4290 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4291 else: 4292 # Snowflake case: CREDENTIALS = (...) 4293 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4294 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4295 4296 storage = self.sql(expression, "storage") 4297 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4298 4299 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4300 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4301 4302 iam_role = self.sql(expression, "iam_role") 4303 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4304 4305 region = self.sql(expression, "region") 4306 region = f" REGION {region}" if region else "" 4307 4308 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4309 4310 def copy_sql(self, expression: exp.Copy) -> str: 4311 this = self.sql(expression, "this") 4312 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4313 4314 credentials = self.sql(expression, "credentials") 4315 credentials = self.seg(credentials) if credentials else "" 4316 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4317 files = self.expressions(expression, key="files", flat=True) 4318 4319 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4320 params = self.expressions( 4321 expression, 4322 key="params", 4323 sep=sep, 4324 new_line=True, 4325 skip_last=True, 4326 skip_first=True, 4327 indent=self.COPY_PARAMS_ARE_WRAPPED, 4328 ) 4329 4330 if params: 4331 if self.COPY_PARAMS_ARE_WRAPPED: 4332 params = f" WITH ({params})" 4333 elif not self.pretty: 4334 params = f" {params}" 4335 4336 return f"COPY{this}{kind} {files}{credentials}{params}" 4337 4338 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4339 return "" 4340 4341 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4342 on_sql = "ON" if expression.args.get("on") else "OFF" 4343 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4344 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4345 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4346 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4347 4348 if filter_col or retention_period: 4349 on_sql = self.func("ON", filter_col, retention_period) 4350 4351 return f"DATA_DELETION={on_sql}" 4352 4353 def maskingpolicycolumnconstraint_sql( 4354 self, expression: exp.MaskingPolicyColumnConstraint 4355 ) -> str: 4356 this = self.sql(expression, "this") 4357 expressions = self.expressions(expression, flat=True) 4358 expressions = f" USING ({expressions})" if expressions else "" 4359 return f"MASKING POLICY {this}{expressions}" 4360 4361 def gapfill_sql(self, expression: exp.GapFill) -> str: 4362 this = self.sql(expression, "this") 4363 this = f"TABLE {this}" 4364 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4365 4366 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4367 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4368 4369 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4370 this = self.sql(expression, "this") 4371 expr = expression.expression 4372 4373 if isinstance(expr, exp.Func): 4374 # T-SQL's CLR functions are case sensitive 4375 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4376 else: 4377 expr = self.sql(expression, "expression") 4378 4379 return self.scope_resolution(expr, this) 4380 4381 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4382 if self.PARSE_JSON_NAME is None: 4383 return self.sql(expression.this) 4384 4385 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4386 4387 def rand_sql(self, expression: exp.Rand) -> str: 4388 lower = self.sql(expression, "lower") 4389 upper = self.sql(expression, "upper") 4390 4391 if lower and upper: 4392 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4393 return self.func("RAND", expression.this) 4394 4395 def changes_sql(self, expression: exp.Changes) -> str: 4396 information = self.sql(expression, "information") 4397 information = f"INFORMATION => {information}" 4398 at_before = self.sql(expression, "at_before") 4399 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4400 end = self.sql(expression, "end") 4401 end = f"{self.seg('')}{end}" if end else "" 4402 4403 return f"CHANGES ({information}){at_before}{end}" 4404 4405 def pad_sql(self, expression: exp.Pad) -> str: 4406 prefix = "L" if expression.args.get("is_left") else "R" 4407 4408 fill_pattern = self.sql(expression, "fill_pattern") or None 4409 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4410 fill_pattern = "' '" 4411 4412 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4413 4414 def summarize_sql(self, expression: exp.Summarize) -> str: 4415 table = " TABLE" if expression.args.get("table") else "" 4416 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4417 4418 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4419 generate_series = exp.GenerateSeries(**expression.args) 4420 4421 parent = expression.parent 4422 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4423 parent = parent.parent 4424 4425 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4426 return self.sql(exp.Unnest(expressions=[generate_series])) 4427 4428 if isinstance(parent, exp.Select): 4429 self.unsupported("GenerateSeries projection unnesting is not supported.") 4430 4431 return self.sql(generate_series) 4432 4433 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4434 exprs = expression.expressions 4435 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4436 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4437 else: 4438 rhs = self.expressions(expression) 4439 4440 return self.func(name, expression.this, rhs or None) 4441 4442 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4443 if self.SUPPORTS_CONVERT_TIMEZONE: 4444 return self.function_fallback_sql(expression) 4445 4446 source_tz = expression.args.get("source_tz") 4447 target_tz = expression.args.get("target_tz") 4448 timestamp = expression.args.get("timestamp") 4449 4450 if source_tz and timestamp: 4451 timestamp = exp.AtTimeZone( 4452 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4453 ) 4454 4455 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4456 4457 return self.sql(expr) 4458 4459 def json_sql(self, expression: exp.JSON) -> str: 4460 this = self.sql(expression, "this") 4461 this = f" {this}" if this else "" 4462 4463 _with = expression.args.get("with") 4464 4465 if _with is None: 4466 with_sql = "" 4467 elif not _with: 4468 with_sql = " WITHOUT" 4469 else: 4470 with_sql = " WITH" 4471 4472 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4473 4474 return f"JSON{this}{with_sql}{unique_sql}" 4475 4476 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4477 def _generate_on_options(arg: t.Any) -> str: 4478 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4479 4480 path = self.sql(expression, "path") 4481 returning = self.sql(expression, "returning") 4482 returning = f" RETURNING {returning}" if returning else "" 4483 4484 on_condition = self.sql(expression, "on_condition") 4485 on_condition = f" {on_condition}" if on_condition else "" 4486 4487 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4488 4489 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4490 else_ = "ELSE " if expression.args.get("else_") else "" 4491 condition = self.sql(expression, "expression") 4492 condition = f"WHEN {condition} THEN " if condition else else_ 4493 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4494 return f"{condition}{insert}" 4495 4496 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4497 kind = self.sql(expression, "kind") 4498 expressions = self.seg(self.expressions(expression, sep=" ")) 4499 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4500 return res 4501 4502 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4503 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4504 empty = expression.args.get("empty") 4505 empty = ( 4506 f"DEFAULT {empty} ON EMPTY" 4507 if isinstance(empty, exp.Expression) 4508 else self.sql(expression, "empty") 4509 ) 4510 4511 error = expression.args.get("error") 4512 error = ( 4513 f"DEFAULT {error} ON ERROR" 4514 if isinstance(error, exp.Expression) 4515 else self.sql(expression, "error") 4516 ) 4517 4518 if error and empty: 4519 error = ( 4520 f"{empty} {error}" 4521 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4522 else f"{error} {empty}" 4523 ) 4524 empty = "" 4525 4526 null = self.sql(expression, "null") 4527 4528 return f"{empty}{error}{null}" 4529 4530 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4531 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4532 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4533 4534 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4535 this = self.sql(expression, "this") 4536 path = self.sql(expression, "path") 4537 4538 passing = self.expressions(expression, "passing") 4539 passing = f" PASSING {passing}" if passing else "" 4540 4541 on_condition = self.sql(expression, "on_condition") 4542 on_condition = f" {on_condition}" if on_condition else "" 4543 4544 path = f"{path}{passing}{on_condition}" 4545 4546 return self.func("JSON_EXISTS", this, path) 4547 4548 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4549 array_agg = self.function_fallback_sql(expression) 4550 4551 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4552 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4553 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4554 parent = expression.parent 4555 if isinstance(parent, exp.Filter): 4556 parent_cond = parent.expression.this 4557 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4558 else: 4559 this = expression.this 4560 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4561 if this.find(exp.Column): 4562 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4563 this_sql = ( 4564 self.expressions(this) 4565 if isinstance(this, exp.Distinct) 4566 else self.sql(expression, "this") 4567 ) 4568 4569 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4570 4571 return array_agg 4572 4573 def apply_sql(self, expression: exp.Apply) -> str: 4574 this = self.sql(expression, "this") 4575 expr = self.sql(expression, "expression") 4576 4577 return f"{this} APPLY({expr})" 4578 4579 def grant_sql(self, expression: exp.Grant) -> str: 4580 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4581 4582 kind = self.sql(expression, "kind") 4583 kind = f" {kind}" if kind else "" 4584 4585 securable = self.sql(expression, "securable") 4586 securable = f" {securable}" if securable else "" 4587 4588 principals = self.expressions(expression, key="principals", flat=True) 4589 4590 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4591 4592 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4593 4594 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4595 this = self.sql(expression, "this") 4596 columns = self.expressions(expression, flat=True) 4597 columns = f"({columns})" if columns else "" 4598 4599 return f"{this}{columns}" 4600 4601 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4602 this = self.sql(expression, "this") 4603 4604 kind = self.sql(expression, "kind") 4605 kind = f"{kind} " if kind else "" 4606 4607 return f"{kind}{this}" 4608 4609 def columns_sql(self, expression: exp.Columns): 4610 func = self.function_fallback_sql(expression) 4611 if expression.args.get("unpack"): 4612 func = f"*{func}" 4613 4614 return func 4615 4616 def overlay_sql(self, expression: exp.Overlay): 4617 this = self.sql(expression, "this") 4618 expr = self.sql(expression, "expression") 4619 from_sql = self.sql(expression, "from") 4620 for_sql = self.sql(expression, "for") 4621 for_sql = f" FOR {for_sql}" if for_sql else "" 4622 4623 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4624 4625 @unsupported_args("format") 4626 def todouble_sql(self, expression: exp.ToDouble) -> str: 4627 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4628 4629 def string_sql(self, expression: exp.String) -> str: 4630 this = expression.this 4631 zone = expression.args.get("zone") 4632 4633 if zone: 4634 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4635 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4636 # set for source_tz to transpile the time conversion before the STRING cast 4637 this = exp.ConvertTimezone( 4638 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4639 ) 4640 4641 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4642 4643 def median_sql(self, expression: exp.Median): 4644 if not self.SUPPORTS_MEDIAN: 4645 return self.sql( 4646 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4647 ) 4648 4649 return self.function_fallback_sql(expression) 4650 4651 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4652 filler = self.sql(expression, "this") 4653 filler = f" {filler}" if filler else "" 4654 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4655 return f"TRUNCATE{filler} {with_count}" 4656 4657 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4658 if self.SUPPORTS_UNIX_SECONDS: 4659 return self.function_fallback_sql(expression) 4660 4661 start_ts = exp.cast( 4662 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4663 ) 4664 4665 return self.sql( 4666 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4667 ) 4668 4669 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4670 dim = expression.expression 4671 4672 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4673 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4674 if not (dim.is_int and dim.name == "1"): 4675 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4676 dim = None 4677 4678 # If dimension is required but not specified, default initialize it 4679 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4680 dim = exp.Literal.number(1) 4681 4682 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4683 4684 def attach_sql(self, expression: exp.Attach) -> str: 4685 this = self.sql(expression, "this") 4686 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4687 expressions = self.expressions(expression) 4688 expressions = f" ({expressions})" if expressions else "" 4689 4690 return f"ATTACH{exists_sql} {this}{expressions}" 4691 4692 def detach_sql(self, expression: exp.Detach) -> str: 4693 this = self.sql(expression, "this") 4694 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 4695 4696 return f"DETACH{exists_sql} {this}" 4697 4698 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4699 this = self.sql(expression, "this") 4700 value = self.sql(expression, "expression") 4701 value = f" {value}" if value else "" 4702 return f"{this}{value}" 4703 4704 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4705 this_sql = self.sql(expression, "this") 4706 if isinstance(expression.this, exp.Table): 4707 this_sql = f"TABLE {this_sql}" 4708 4709 return self.func( 4710 "FEATURES_AT_TIME", 4711 this_sql, 4712 expression.args.get("time"), 4713 expression.args.get("num_rows"), 4714 expression.args.get("ignore_feature_nulls"), 4715 ) 4716 4717 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4718 return ( 4719 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4720 ) 4721 4722 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4723 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4724 encode = f"{encode} {self.sql(expression, 'this')}" 4725 4726 properties = expression.args.get("properties") 4727 if properties: 4728 encode = f"{encode} {self.properties(properties)}" 4729 4730 return encode 4731 4732 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4733 this = self.sql(expression, "this") 4734 include = f"INCLUDE {this}" 4735 4736 column_def = self.sql(expression, "column_def") 4737 if column_def: 4738 include = f"{include} {column_def}" 4739 4740 alias = self.sql(expression, "alias") 4741 if alias: 4742 include = f"{include} AS {alias}" 4743 4744 return include 4745 4746 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4747 name = f"NAME {self.sql(expression, 'this')}" 4748 return self.func("XMLELEMENT", name, *expression.expressions) 4749 4750 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4751 partitions = self.expressions(expression, "partition_expressions") 4752 create = self.expressions(expression, "create_expressions") 4753 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4754 4755 def partitionbyrangepropertydynamic_sql( 4756 self, expression: exp.PartitionByRangePropertyDynamic 4757 ) -> str: 4758 start = self.sql(expression, "start") 4759 end = self.sql(expression, "end") 4760 4761 every = expression.args["every"] 4762 if isinstance(every, exp.Interval) and every.this.is_string: 4763 every.this.replace(exp.Literal.number(every.name)) 4764 4765 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4766 4767 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4768 name = self.sql(expression, "this") 4769 values = self.expressions(expression, flat=True) 4770 4771 return f"NAME {name} VALUE {values}" 4772 4773 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4774 kind = self.sql(expression, "kind") 4775 sample = self.sql(expression, "sample") 4776 return f"SAMPLE {sample} {kind}" 4777 4778 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4779 kind = self.sql(expression, "kind") 4780 option = self.sql(expression, "option") 4781 option = f" {option}" if option else "" 4782 this = self.sql(expression, "this") 4783 this = f" {this}" if this else "" 4784 columns = self.expressions(expression) 4785 columns = f" {columns}" if columns else "" 4786 return f"{kind}{option} STATISTICS{this}{columns}" 4787 4788 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4789 this = self.sql(expression, "this") 4790 columns = self.expressions(expression) 4791 inner_expression = self.sql(expression, "expression") 4792 inner_expression = f" {inner_expression}" if inner_expression else "" 4793 update_options = self.sql(expression, "update_options") 4794 update_options = f" {update_options} UPDATE" if update_options else "" 4795 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4796 4797 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4798 kind = self.sql(expression, "kind") 4799 kind = f" {kind}" if kind else "" 4800 return f"DELETE{kind} STATISTICS" 4801 4802 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4803 inner_expression = self.sql(expression, "expression") 4804 return f"LIST CHAINED ROWS{inner_expression}" 4805 4806 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4807 kind = self.sql(expression, "kind") 4808 this = self.sql(expression, "this") 4809 this = f" {this}" if this else "" 4810 inner_expression = self.sql(expression, "expression") 4811 return f"VALIDATE {kind}{this}{inner_expression}" 4812 4813 def analyze_sql(self, expression: exp.Analyze) -> str: 4814 options = self.expressions(expression, key="options", sep=" ") 4815 options = f" {options}" if options else "" 4816 kind = self.sql(expression, "kind") 4817 kind = f" {kind}" if kind else "" 4818 this = self.sql(expression, "this") 4819 this = f" {this}" if this else "" 4820 mode = self.sql(expression, "mode") 4821 mode = f" {mode}" if mode else "" 4822 properties = self.sql(expression, "properties") 4823 properties = f" {properties}" if properties else "" 4824 partition = self.sql(expression, "partition") 4825 partition = f" {partition}" if partition else "" 4826 inner_expression = self.sql(expression, "expression") 4827 inner_expression = f" {inner_expression}" if inner_expression else "" 4828 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4829 4830 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4831 this = self.sql(expression, "this") 4832 namespaces = self.expressions(expression, key="namespaces") 4833 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4834 passing = self.expressions(expression, key="passing") 4835 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4836 columns = self.expressions(expression, key="columns") 4837 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4838 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4839 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4840 4841 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4842 this = self.sql(expression, "this") 4843 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4844 4845 def export_sql(self, expression: exp.Export) -> str: 4846 this = self.sql(expression, "this") 4847 connection = self.sql(expression, "connection") 4848 connection = f"WITH CONNECTION {connection} " if connection else "" 4849 options = self.sql(expression, "options") 4850 return f"EXPORT DATA {connection}{options} AS {this}" 4851 4852 def declare_sql(self, expression: exp.Declare) -> str: 4853 return f"DECLARE {self.expressions(expression, flat=True)}" 4854 4855 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4856 variable = self.sql(expression, "this") 4857 default = self.sql(expression, "default") 4858 default = f" = {default}" if default else "" 4859 4860 kind = self.sql(expression, "kind") 4861 if isinstance(expression.args.get("kind"), exp.Schema): 4862 kind = f"TABLE {kind}" 4863 4864 return f"{variable} AS {kind}{default}" 4865 4866 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4867 kind = self.sql(expression, "kind") 4868 this = self.sql(expression, "this") 4869 set = self.sql(expression, "expression") 4870 using = self.sql(expression, "using") 4871 using = f" USING {using}" if using else "" 4872 4873 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4874 4875 return f"{kind_sql} {this} SET {set}{using}" 4876 4877 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4878 params = self.expressions(expression, key="params", flat=True) 4879 return self.func(expression.name, *expression.expressions) + f"({params})" 4880 4881 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4882 return self.func(expression.name, *expression.expressions) 4883 4884 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4885 return self.anonymousaggfunc_sql(expression) 4886 4887 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 4888 return self.parameterizedagg_sql(expression) 4889 4890 def show_sql(self, expression: exp.Show) -> str: 4891 self.unsupported("Unsupported SHOW statement") 4892 return "" 4893 4894 def put_sql(self, expression: exp.Put) -> str: 4895 props = expression.args.get("properties") 4896 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4897 this = self.sql(expression, "this") 4898 target = self.sql(expression, "target") 4899 return f"PUT {this} {target}{props_sql}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
681 def __init__( 682 self, 683 pretty: t.Optional[bool] = None, 684 identify: str | bool = False, 685 normalize: bool = False, 686 pad: int = 2, 687 indent: int = 2, 688 normalize_functions: t.Optional[str | bool] = None, 689 unsupported_level: ErrorLevel = ErrorLevel.WARN, 690 max_unsupported: int = 3, 691 leading_comma: bool = False, 692 max_text_width: int = 80, 693 comments: bool = True, 694 dialect: DialectType = None, 695 ): 696 import sqlglot 697 from sqlglot.dialects import Dialect 698 699 self.pretty = pretty if pretty is not None else sqlglot.pretty 700 self.identify = identify 701 self.normalize = normalize 702 self.pad = pad 703 self._indent = indent 704 self.unsupported_level = unsupported_level 705 self.max_unsupported = max_unsupported 706 self.leading_comma = leading_comma 707 self.max_text_width = max_text_width 708 self.comments = comments 709 self.dialect = Dialect.get_or_raise(dialect) 710 711 # This is both a Dialect property and a Generator argument, so we prioritize the latter 712 self.normalize_functions = ( 713 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 714 ) 715 716 self.unsupported_messages: t.List[str] = [] 717 self._escaped_quote_end: str = ( 718 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 719 ) 720 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 721 722 self._next_name = name_sequence("_t") 723 724 self._identifier_start = self.dialect.IDENTIFIER_START 725 self._identifier_end = self.dialect.IDENTIFIER_END 726 727 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.CHAR: 'CHAR'>}
729 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 730 """ 731 Generates the SQL string corresponding to the given syntax tree. 732 733 Args: 734 expression: The syntax tree. 735 copy: Whether to copy the expression. The generator performs mutations so 736 it is safer to copy. 737 738 Returns: 739 The SQL string corresponding to `expression`. 740 """ 741 if copy: 742 expression = expression.copy() 743 744 expression = self.preprocess(expression) 745 746 self.unsupported_messages = [] 747 sql = self.sql(expression).strip() 748 749 if self.pretty: 750 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 751 752 if self.unsupported_level == ErrorLevel.IGNORE: 753 return sql 754 755 if self.unsupported_level == ErrorLevel.WARN: 756 for msg in self.unsupported_messages: 757 logger.warning(msg) 758 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 759 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 760 761 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
763 def preprocess(self, expression: exp.Expression) -> exp.Expression: 764 """Apply generic preprocessing transformations to a given expression.""" 765 expression = self._move_ctes_to_top_level(expression) 766 767 if self.ENSURE_BOOLS: 768 from sqlglot.transforms import ensure_bools 769 770 expression = ensure_bools(expression) 771 772 return expression
Apply generic preprocessing transformations to a given expression.
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
801 def maybe_comment( 802 self, 803 sql: str, 804 expression: t.Optional[exp.Expression] = None, 805 comments: t.Optional[t.List[str]] = None, 806 separated: bool = False, 807 ) -> str: 808 comments = ( 809 ((expression and expression.comments) if comments is None else comments) # type: ignore 810 if self.comments 811 else None 812 ) 813 814 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 815 return sql 816 817 comments_sql = " ".join( 818 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 819 ) 820 821 if not comments_sql: 822 return sql 823 824 comments_sql = self._replace_line_breaks(comments_sql) 825 826 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 827 return ( 828 f"{self.sep()}{comments_sql}{sql}" 829 if not sql or sql[0].isspace() 830 else f"{comments_sql}{self.sep()}{sql}" 831 ) 832 833 return f"{sql} {comments_sql}"
835 def wrap(self, expression: exp.Expression | str) -> str: 836 this_sql = ( 837 self.sql(expression) 838 if isinstance(expression, exp.UNWRAPPED_QUERIES) 839 else self.sql(expression, "this") 840 ) 841 if not this_sql: 842 return "()" 843 844 this_sql = self.indent(this_sql, level=1, pad=0) 845 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
861 def indent( 862 self, 863 sql: str, 864 level: int = 0, 865 pad: t.Optional[int] = None, 866 skip_first: bool = False, 867 skip_last: bool = False, 868 ) -> str: 869 if not self.pretty or not sql: 870 return sql 871 872 pad = self.pad if pad is None else pad 873 lines = sql.split("\n") 874 875 return "\n".join( 876 ( 877 line 878 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 879 else f"{' ' * (level * self._indent + pad)}{line}" 880 ) 881 for i, line in enumerate(lines) 882 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
884 def sql( 885 self, 886 expression: t.Optional[str | exp.Expression], 887 key: t.Optional[str] = None, 888 comment: bool = True, 889 ) -> str: 890 if not expression: 891 return "" 892 893 if isinstance(expression, str): 894 return expression 895 896 if key: 897 value = expression.args.get(key) 898 if value: 899 return self.sql(value) 900 return "" 901 902 transform = self.TRANSFORMS.get(expression.__class__) 903 904 if callable(transform): 905 sql = transform(self, expression) 906 elif isinstance(expression, exp.Expression): 907 exp_handler_name = f"{expression.key}_sql" 908 909 if hasattr(self, exp_handler_name): 910 sql = getattr(self, exp_handler_name)(expression) 911 elif isinstance(expression, exp.Func): 912 sql = self.function_fallback_sql(expression) 913 elif isinstance(expression, exp.Property): 914 sql = self.property_sql(expression) 915 else: 916 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 917 else: 918 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 919 920 return self.maybe_comment(sql, expression) if self.comments and comment else sql
927 def cache_sql(self, expression: exp.Cache) -> str: 928 lazy = " LAZY" if expression.args.get("lazy") else "" 929 table = self.sql(expression, "this") 930 options = expression.args.get("options") 931 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 932 sql = self.sql(expression, "expression") 933 sql = f" AS{self.sep()}{sql}" if sql else "" 934 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 935 return self.prepend_ctes(expression, sql)
937 def characterset_sql(self, expression: exp.CharacterSet) -> str: 938 if isinstance(expression.parent, exp.Cast): 939 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 940 default = "DEFAULT " if expression.args.get("default") else "" 941 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
955 def column_sql(self, expression: exp.Column) -> str: 956 join_mark = " (+)" if expression.args.get("join_mark") else "" 957 958 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 959 join_mark = "" 960 self.unsupported("Outer join syntax using the (+) operator is not supported.") 961 962 return f"{self.column_parts(expression)}{join_mark}"
970 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 971 column = self.sql(expression, "this") 972 kind = self.sql(expression, "kind") 973 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 974 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 975 kind = f"{sep}{kind}" if kind else "" 976 constraints = f" {constraints}" if constraints else "" 977 position = self.sql(expression, "position") 978 position = f" {position}" if position else "" 979 980 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 981 kind = "" 982 983 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
990 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 991 this = self.sql(expression, "this") 992 if expression.args.get("not_null"): 993 persisted = " PERSISTED NOT NULL" 994 elif expression.args.get("persisted"): 995 persisted = " PERSISTED" 996 else: 997 persisted = "" 998 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1011 def generatedasidentitycolumnconstraint_sql( 1012 self, expression: exp.GeneratedAsIdentityColumnConstraint 1013 ) -> str: 1014 this = "" 1015 if expression.this is not None: 1016 on_null = " ON NULL" if expression.args.get("on_null") else "" 1017 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1018 1019 start = expression.args.get("start") 1020 start = f"START WITH {start}" if start else "" 1021 increment = expression.args.get("increment") 1022 increment = f" INCREMENT BY {increment}" if increment else "" 1023 minvalue = expression.args.get("minvalue") 1024 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1025 maxvalue = expression.args.get("maxvalue") 1026 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1027 cycle = expression.args.get("cycle") 1028 cycle_sql = "" 1029 1030 if cycle is not None: 1031 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1032 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1033 1034 sequence_opts = "" 1035 if start or increment or cycle_sql: 1036 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1037 sequence_opts = f" ({sequence_opts.strip()})" 1038 1039 expr = self.sql(expression, "expression") 1040 expr = f"({expr})" if expr else "IDENTITY" 1041 1042 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1044 def generatedasrowcolumnconstraint_sql( 1045 self, expression: exp.GeneratedAsRowColumnConstraint 1046 ) -> str: 1047 start = "START" if expression.args.get("start") else "END" 1048 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1049 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
transformcolumnconstraint_sql(self, expression: sqlglot.expressions.TransformColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1068 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1069 this = self.sql(expression, "this") 1070 this = f" {this}" if this else "" 1071 index_type = expression.args.get("index_type") 1072 index_type = f" USING {index_type}" if index_type else "" 1073 on_conflict = self.sql(expression, "on_conflict") 1074 on_conflict = f" {on_conflict}" if on_conflict else "" 1075 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1076 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}"
1081 def create_sql(self, expression: exp.Create) -> str: 1082 kind = self.sql(expression, "kind") 1083 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1084 properties = expression.args.get("properties") 1085 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1086 1087 this = self.createable_sql(expression, properties_locs) 1088 1089 properties_sql = "" 1090 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1091 exp.Properties.Location.POST_WITH 1092 ): 1093 properties_sql = self.sql( 1094 exp.Properties( 1095 expressions=[ 1096 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1097 *properties_locs[exp.Properties.Location.POST_WITH], 1098 ] 1099 ) 1100 ) 1101 1102 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1103 properties_sql = self.sep() + properties_sql 1104 elif not self.pretty: 1105 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1106 properties_sql = f" {properties_sql}" 1107 1108 begin = " BEGIN" if expression.args.get("begin") else "" 1109 end = " END" if expression.args.get("end") else "" 1110 1111 expression_sql = self.sql(expression, "expression") 1112 if expression_sql: 1113 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1114 1115 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1116 postalias_props_sql = "" 1117 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1118 postalias_props_sql = self.properties( 1119 exp.Properties( 1120 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1121 ), 1122 wrapped=False, 1123 ) 1124 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1125 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1126 1127 postindex_props_sql = "" 1128 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1129 postindex_props_sql = self.properties( 1130 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1131 wrapped=False, 1132 prefix=" ", 1133 ) 1134 1135 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1136 indexes = f" {indexes}" if indexes else "" 1137 index_sql = indexes + postindex_props_sql 1138 1139 replace = " OR REPLACE" if expression.args.get("replace") else "" 1140 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1141 unique = " UNIQUE" if expression.args.get("unique") else "" 1142 1143 clustered = expression.args.get("clustered") 1144 if clustered is None: 1145 clustered_sql = "" 1146 elif clustered: 1147 clustered_sql = " CLUSTERED COLUMNSTORE" 1148 else: 1149 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1150 1151 postcreate_props_sql = "" 1152 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1153 postcreate_props_sql = self.properties( 1154 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1155 sep=" ", 1156 prefix=" ", 1157 wrapped=False, 1158 ) 1159 1160 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1161 1162 postexpression_props_sql = "" 1163 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1164 postexpression_props_sql = self.properties( 1165 exp.Properties( 1166 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1167 ), 1168 sep=" ", 1169 prefix=" ", 1170 wrapped=False, 1171 ) 1172 1173 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1174 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1175 no_schema_binding = ( 1176 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1177 ) 1178 1179 clone = self.sql(expression, "clone") 1180 clone = f" {clone}" if clone else "" 1181 1182 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1183 properties_expression = f"{expression_sql}{properties_sql}" 1184 else: 1185 properties_expression = f"{properties_sql}{expression_sql}" 1186 1187 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1188 return self.prepend_ctes(expression, expression_sql)
1190 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1191 start = self.sql(expression, "start") 1192 start = f"START WITH {start}" if start else "" 1193 increment = self.sql(expression, "increment") 1194 increment = f" INCREMENT BY {increment}" if increment else "" 1195 minvalue = self.sql(expression, "minvalue") 1196 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1197 maxvalue = self.sql(expression, "maxvalue") 1198 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1199 owned = self.sql(expression, "owned") 1200 owned = f" OWNED BY {owned}" if owned else "" 1201 1202 cache = expression.args.get("cache") 1203 if cache is None: 1204 cache_str = "" 1205 elif cache is True: 1206 cache_str = " CACHE" 1207 else: 1208 cache_str = f" CACHE {cache}" 1209 1210 options = self.expressions(expression, key="options", flat=True, sep=" ") 1211 options = f" {options}" if options else "" 1212 1213 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1215 def clone_sql(self, expression: exp.Clone) -> str: 1216 this = self.sql(expression, "this") 1217 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1218 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1219 return f"{shallow}{keyword} {this}"
1221 def describe_sql(self, expression: exp.Describe) -> str: 1222 style = expression.args.get("style") 1223 style = f" {style}" if style else "" 1224 partition = self.sql(expression, "partition") 1225 partition = f" {partition}" if partition else "" 1226 format = self.sql(expression, "format") 1227 format = f" {format}" if format else "" 1228 1229 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1241 def with_sql(self, expression: exp.With) -> str: 1242 sql = self.expressions(expression, flat=True) 1243 recursive = ( 1244 "RECURSIVE " 1245 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1246 else "" 1247 ) 1248 search = self.sql(expression, "search") 1249 search = f" {search}" if search else "" 1250 1251 return f"WITH {recursive}{sql}{search}"
1253 def cte_sql(self, expression: exp.CTE) -> str: 1254 alias = expression.args.get("alias") 1255 if alias: 1256 alias.add_comments(expression.pop_comments()) 1257 1258 alias_sql = self.sql(expression, "alias") 1259 1260 materialized = expression.args.get("materialized") 1261 if materialized is False: 1262 materialized = "NOT MATERIALIZED " 1263 elif materialized: 1264 materialized = "MATERIALIZED " 1265 1266 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1268 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1269 alias = self.sql(expression, "this") 1270 columns = self.expressions(expression, key="columns", flat=True) 1271 columns = f"({columns})" if columns else "" 1272 1273 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1274 columns = "" 1275 self.unsupported("Named columns are not supported in table alias.") 1276 1277 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1278 alias = self._next_name() 1279 1280 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1288 def hexstring_sql( 1289 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1290 ) -> str: 1291 this = self.sql(expression, "this") 1292 is_integer_type = expression.args.get("is_integer") 1293 1294 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1295 not self.dialect.HEX_START and not binary_function_repr 1296 ): 1297 # Integer representation will be returned if: 1298 # - The read dialect treats the hex value as integer literal but not the write 1299 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1300 return f"{int(this, 16)}" 1301 1302 if not is_integer_type: 1303 # Read dialect treats the hex value as BINARY/BLOB 1304 if binary_function_repr: 1305 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1306 return self.func(binary_function_repr, exp.Literal.string(this)) 1307 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1308 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1309 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1310 1311 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1319 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1320 this = self.sql(expression, "this") 1321 escape = expression.args.get("escape") 1322 1323 if self.dialect.UNICODE_START: 1324 escape_substitute = r"\\\1" 1325 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1326 else: 1327 escape_substitute = r"\\u\1" 1328 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1329 1330 if escape: 1331 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1332 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1333 else: 1334 escape_pattern = ESCAPED_UNICODE_RE 1335 escape_sql = "" 1336 1337 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1338 this = escape_pattern.sub(escape_substitute, this) 1339 1340 return f"{left_quote}{this}{right_quote}{escape_sql}"
1352 def datatype_sql(self, expression: exp.DataType) -> str: 1353 nested = "" 1354 values = "" 1355 interior = self.expressions(expression, flat=True) 1356 1357 type_value = expression.this 1358 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1359 type_sql = self.sql(expression, "kind") 1360 else: 1361 type_sql = ( 1362 self.TYPE_MAPPING.get(type_value, type_value.value) 1363 if isinstance(type_value, exp.DataType.Type) 1364 else type_value 1365 ) 1366 1367 if interior: 1368 if expression.args.get("nested"): 1369 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1370 if expression.args.get("values") is not None: 1371 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1372 values = self.expressions(expression, key="values", flat=True) 1373 values = f"{delimiters[0]}{values}{delimiters[1]}" 1374 elif type_value == exp.DataType.Type.INTERVAL: 1375 nested = f" {interior}" 1376 else: 1377 nested = f"({interior})" 1378 1379 type_sql = f"{type_sql}{nested}{values}" 1380 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1381 exp.DataType.Type.TIMETZ, 1382 exp.DataType.Type.TIMESTAMPTZ, 1383 ): 1384 type_sql = f"{type_sql} WITH TIME ZONE" 1385 1386 return type_sql
1388 def directory_sql(self, expression: exp.Directory) -> str: 1389 local = "LOCAL " if expression.args.get("local") else "" 1390 row_format = self.sql(expression, "row_format") 1391 row_format = f" {row_format}" if row_format else "" 1392 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1394 def delete_sql(self, expression: exp.Delete) -> str: 1395 this = self.sql(expression, "this") 1396 this = f" FROM {this}" if this else "" 1397 using = self.sql(expression, "using") 1398 using = f" USING {using}" if using else "" 1399 cluster = self.sql(expression, "cluster") 1400 cluster = f" {cluster}" if cluster else "" 1401 where = self.sql(expression, "where") 1402 returning = self.sql(expression, "returning") 1403 limit = self.sql(expression, "limit") 1404 tables = self.expressions(expression, key="tables") 1405 tables = f" {tables}" if tables else "" 1406 if self.RETURNING_END: 1407 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1408 else: 1409 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1410 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1412 def drop_sql(self, expression: exp.Drop) -> str: 1413 this = self.sql(expression, "this") 1414 expressions = self.expressions(expression, flat=True) 1415 expressions = f" ({expressions})" if expressions else "" 1416 kind = expression.args["kind"] 1417 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1418 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1419 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1420 on_cluster = self.sql(expression, "cluster") 1421 on_cluster = f" {on_cluster}" if on_cluster else "" 1422 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1423 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1424 cascade = " CASCADE" if expression.args.get("cascade") else "" 1425 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1426 purge = " PURGE" if expression.args.get("purge") else "" 1427 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1429 def set_operation(self, expression: exp.SetOperation) -> str: 1430 op_type = type(expression) 1431 op_name = op_type.key.upper() 1432 1433 distinct = expression.args.get("distinct") 1434 if ( 1435 distinct is False 1436 and op_type in (exp.Except, exp.Intersect) 1437 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1438 ): 1439 self.unsupported(f"{op_name} ALL is not supported") 1440 1441 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1442 1443 if distinct is None: 1444 distinct = default_distinct 1445 if distinct is None: 1446 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1447 1448 if distinct is default_distinct: 1449 distinct_or_all = "" 1450 else: 1451 distinct_or_all = " DISTINCT" if distinct else " ALL" 1452 1453 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1454 side_kind = f"{side_kind} " if side_kind else "" 1455 1456 by_name = " BY NAME" if expression.args.get("by_name") else "" 1457 on = self.expressions(expression, key="on", flat=True) 1458 on = f" ON ({on})" if on else "" 1459 1460 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1462 def set_operations(self, expression: exp.SetOperation) -> str: 1463 if not self.SET_OP_MODIFIERS: 1464 limit = expression.args.get("limit") 1465 order = expression.args.get("order") 1466 1467 if limit or order: 1468 select = self._move_ctes_to_top_level( 1469 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1470 ) 1471 1472 if limit: 1473 select = select.limit(limit.pop(), copy=False) 1474 if order: 1475 select = select.order_by(order.pop(), copy=False) 1476 return self.sql(select) 1477 1478 sqls: t.List[str] = [] 1479 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1480 1481 while stack: 1482 node = stack.pop() 1483 1484 if isinstance(node, exp.SetOperation): 1485 stack.append(node.expression) 1486 stack.append( 1487 self.maybe_comment( 1488 self.set_operation(node), comments=node.comments, separated=True 1489 ) 1490 ) 1491 stack.append(node.this) 1492 else: 1493 sqls.append(self.sql(node)) 1494 1495 this = self.sep().join(sqls) 1496 this = self.query_modifiers(expression, this) 1497 return self.prepend_ctes(expression, this)
1499 def fetch_sql(self, expression: exp.Fetch) -> str: 1500 direction = expression.args.get("direction") 1501 direction = f" {direction}" if direction else "" 1502 count = self.sql(expression, "count") 1503 count = f" {count}" if count else "" 1504 limit_options = self.sql(expression, "limit_options") 1505 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1506 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1508 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1509 percent = " PERCENT" if expression.args.get("percent") else "" 1510 rows = " ROWS" if expression.args.get("rows") else "" 1511 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1512 if not with_ties and rows: 1513 with_ties = " ONLY" 1514 return f"{percent}{rows}{with_ties}"
1516 def filter_sql(self, expression: exp.Filter) -> str: 1517 if self.AGGREGATE_FILTER_SUPPORTED: 1518 this = self.sql(expression, "this") 1519 where = self.sql(expression, "expression").strip() 1520 return f"{this} FILTER({where})" 1521 1522 agg = expression.this 1523 agg_arg = agg.this 1524 cond = expression.expression.this 1525 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1526 return self.sql(agg)
1535 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1536 using = self.sql(expression, "using") 1537 using = f" USING {using}" if using else "" 1538 columns = self.expressions(expression, key="columns", flat=True) 1539 columns = f"({columns})" if columns else "" 1540 partition_by = self.expressions(expression, key="partition_by", flat=True) 1541 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1542 where = self.sql(expression, "where") 1543 include = self.expressions(expression, key="include", flat=True) 1544 if include: 1545 include = f" INCLUDE ({include})" 1546 with_storage = self.expressions(expression, key="with_storage", flat=True) 1547 with_storage = f" WITH ({with_storage})" if with_storage else "" 1548 tablespace = self.sql(expression, "tablespace") 1549 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1550 on = self.sql(expression, "on") 1551 on = f" ON {on}" if on else "" 1552 1553 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1555 def index_sql(self, expression: exp.Index) -> str: 1556 unique = "UNIQUE " if expression.args.get("unique") else "" 1557 primary = "PRIMARY " if expression.args.get("primary") else "" 1558 amp = "AMP " if expression.args.get("amp") else "" 1559 name = self.sql(expression, "this") 1560 name = f"{name} " if name else "" 1561 table = self.sql(expression, "table") 1562 table = f"{self.INDEX_ON} {table}" if table else "" 1563 1564 index = "INDEX " if not table else "" 1565 1566 params = self.sql(expression, "params") 1567 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1569 def identifier_sql(self, expression: exp.Identifier) -> str: 1570 text = expression.name 1571 lower = text.lower() 1572 text = lower if self.normalize and not expression.quoted else text 1573 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1574 if ( 1575 expression.quoted 1576 or self.dialect.can_identify(text, self.identify) 1577 or lower in self.RESERVED_KEYWORDS 1578 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1579 ): 1580 text = f"{self._identifier_start}{text}{self._identifier_end}" 1581 return text
1596 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1597 input_format = self.sql(expression, "input_format") 1598 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1599 output_format = self.sql(expression, "output_format") 1600 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1601 return self.sep().join((input_format, output_format))
1611 def properties_sql(self, expression: exp.Properties) -> str: 1612 root_properties = [] 1613 with_properties = [] 1614 1615 for p in expression.expressions: 1616 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1617 if p_loc == exp.Properties.Location.POST_WITH: 1618 with_properties.append(p) 1619 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1620 root_properties.append(p) 1621 1622 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1623 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1624 1625 if root_props and with_props and not self.pretty: 1626 with_props = " " + with_props 1627 1628 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1635 def properties( 1636 self, 1637 properties: exp.Properties, 1638 prefix: str = "", 1639 sep: str = ", ", 1640 suffix: str = "", 1641 wrapped: bool = True, 1642 ) -> str: 1643 if properties.expressions: 1644 expressions = self.expressions(properties, sep=sep, indent=False) 1645 if expressions: 1646 expressions = self.wrap(expressions) if wrapped else expressions 1647 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1648 return ""
1653 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1654 properties_locs = defaultdict(list) 1655 for p in properties.expressions: 1656 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1657 if p_loc != exp.Properties.Location.UNSUPPORTED: 1658 properties_locs[p_loc].append(p) 1659 else: 1660 self.unsupported(f"Unsupported property {p.key}") 1661 1662 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1669 def property_sql(self, expression: exp.Property) -> str: 1670 property_cls = expression.__class__ 1671 if property_cls == exp.Property: 1672 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1673 1674 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1675 if not property_name: 1676 self.unsupported(f"Unsupported property {expression.key}") 1677 1678 return f"{property_name}={self.sql(expression, 'this')}"
1680 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1681 if self.SUPPORTS_CREATE_TABLE_LIKE: 1682 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1683 options = f" {options}" if options else "" 1684 1685 like = f"LIKE {self.sql(expression, 'this')}{options}" 1686 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1687 like = f"({like})" 1688 1689 return like 1690 1691 if expression.expressions: 1692 self.unsupported("Transpilation of LIKE property options is unsupported") 1693 1694 select = exp.select("*").from_(expression.this).limit(0) 1695 return f"AS {self.sql(select)}"
1702 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1703 no = "NO " if expression.args.get("no") else "" 1704 local = expression.args.get("local") 1705 local = f"{local} " if local else "" 1706 dual = "DUAL " if expression.args.get("dual") else "" 1707 before = "BEFORE " if expression.args.get("before") else "" 1708 after = "AFTER " if expression.args.get("after") else "" 1709 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1725 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1726 if expression.args.get("no"): 1727 return "NO MERGEBLOCKRATIO" 1728 if expression.args.get("default"): 1729 return "DEFAULT MERGEBLOCKRATIO" 1730 1731 percent = " PERCENT" if expression.args.get("percent") else "" 1732 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1734 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1735 default = expression.args.get("default") 1736 minimum = expression.args.get("minimum") 1737 maximum = expression.args.get("maximum") 1738 if default or minimum or maximum: 1739 if default: 1740 prop = "DEFAULT" 1741 elif minimum: 1742 prop = "MINIMUM" 1743 else: 1744 prop = "MAXIMUM" 1745 return f"{prop} DATABLOCKSIZE" 1746 units = expression.args.get("units") 1747 units = f" {units}" if units else "" 1748 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1750 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1751 autotemp = expression.args.get("autotemp") 1752 always = expression.args.get("always") 1753 default = expression.args.get("default") 1754 manual = expression.args.get("manual") 1755 never = expression.args.get("never") 1756 1757 if autotemp is not None: 1758 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1759 elif always: 1760 prop = "ALWAYS" 1761 elif default: 1762 prop = "DEFAULT" 1763 elif manual: 1764 prop = "MANUAL" 1765 elif never: 1766 prop = "NEVER" 1767 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1769 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1770 no = expression.args.get("no") 1771 no = " NO" if no else "" 1772 concurrent = expression.args.get("concurrent") 1773 concurrent = " CONCURRENT" if concurrent else "" 1774 target = self.sql(expression, "target") 1775 target = f" {target}" if target else "" 1776 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1778 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1779 if isinstance(expression.this, list): 1780 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1781 if expression.this: 1782 modulus = self.sql(expression, "this") 1783 remainder = self.sql(expression, "expression") 1784 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1785 1786 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1787 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1788 return f"FROM ({from_expressions}) TO ({to_expressions})"
1790 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1791 this = self.sql(expression, "this") 1792 1793 for_values_or_default = expression.expression 1794 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1795 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1796 else: 1797 for_values_or_default = " DEFAULT" 1798 1799 return f"PARTITION OF {this}{for_values_or_default}"
1801 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1802 kind = expression.args.get("kind") 1803 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1804 for_or_in = expression.args.get("for_or_in") 1805 for_or_in = f" {for_or_in}" if for_or_in else "" 1806 lock_type = expression.args.get("lock_type") 1807 override = " OVERRIDE" if expression.args.get("override") else "" 1808 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1810 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1811 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1812 statistics = expression.args.get("statistics") 1813 statistics_sql = "" 1814 if statistics is not None: 1815 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1816 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1818 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1819 this = self.sql(expression, "this") 1820 this = f"HISTORY_TABLE={this}" if this else "" 1821 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1822 data_consistency = ( 1823 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1824 ) 1825 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1826 retention_period = ( 1827 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1828 ) 1829 1830 if this: 1831 on_sql = self.func("ON", this, data_consistency, retention_period) 1832 else: 1833 on_sql = "ON" if expression.args.get("on") else "OFF" 1834 1835 sql = f"SYSTEM_VERSIONING={on_sql}" 1836 1837 return f"WITH({sql})" if expression.args.get("with") else sql
1839 def insert_sql(self, expression: exp.Insert) -> str: 1840 hint = self.sql(expression, "hint") 1841 overwrite = expression.args.get("overwrite") 1842 1843 if isinstance(expression.this, exp.Directory): 1844 this = " OVERWRITE" if overwrite else " INTO" 1845 else: 1846 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1847 1848 stored = self.sql(expression, "stored") 1849 stored = f" {stored}" if stored else "" 1850 alternative = expression.args.get("alternative") 1851 alternative = f" OR {alternative}" if alternative else "" 1852 ignore = " IGNORE" if expression.args.get("ignore") else "" 1853 is_function = expression.args.get("is_function") 1854 if is_function: 1855 this = f"{this} FUNCTION" 1856 this = f"{this} {self.sql(expression, 'this')}" 1857 1858 exists = " IF EXISTS" if expression.args.get("exists") else "" 1859 where = self.sql(expression, "where") 1860 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1861 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1862 on_conflict = self.sql(expression, "conflict") 1863 on_conflict = f" {on_conflict}" if on_conflict else "" 1864 by_name = " BY NAME" if expression.args.get("by_name") else "" 1865 returning = self.sql(expression, "returning") 1866 1867 if self.RETURNING_END: 1868 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1869 else: 1870 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1871 1872 partition_by = self.sql(expression, "partition") 1873 partition_by = f" {partition_by}" if partition_by else "" 1874 settings = self.sql(expression, "settings") 1875 settings = f" {settings}" if settings else "" 1876 1877 source = self.sql(expression, "source") 1878 source = f"TABLE {source}" if source else "" 1879 1880 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1881 return self.prepend_ctes(expression, sql)
1899 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1900 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1901 1902 constraint = self.sql(expression, "constraint") 1903 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1904 1905 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1906 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1907 action = self.sql(expression, "action") 1908 1909 expressions = self.expressions(expression, flat=True) 1910 if expressions: 1911 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1912 expressions = f" {set_keyword}{expressions}" 1913 1914 where = self.sql(expression, "where") 1915 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1920 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1921 fields = self.sql(expression, "fields") 1922 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1923 escaped = self.sql(expression, "escaped") 1924 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1925 items = self.sql(expression, "collection_items") 1926 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1927 keys = self.sql(expression, "map_keys") 1928 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1929 lines = self.sql(expression, "lines") 1930 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1931 null = self.sql(expression, "null") 1932 null = f" NULL DEFINED AS {null}" if null else "" 1933 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1961 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1962 table = self.table_parts(expression) 1963 only = "ONLY " if expression.args.get("only") else "" 1964 partition = self.sql(expression, "partition") 1965 partition = f" {partition}" if partition else "" 1966 version = self.sql(expression, "version") 1967 version = f" {version}" if version else "" 1968 alias = self.sql(expression, "alias") 1969 alias = f"{sep}{alias}" if alias else "" 1970 1971 sample = self.sql(expression, "sample") 1972 if self.dialect.ALIAS_POST_TABLESAMPLE: 1973 sample_pre_alias = sample 1974 sample_post_alias = "" 1975 else: 1976 sample_pre_alias = "" 1977 sample_post_alias = sample 1978 1979 hints = self.expressions(expression, key="hints", sep=" ") 1980 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1981 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1982 joins = self.indent( 1983 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1984 ) 1985 laterals = self.expressions(expression, key="laterals", sep="") 1986 1987 file_format = self.sql(expression, "format") 1988 if file_format: 1989 pattern = self.sql(expression, "pattern") 1990 pattern = f", PATTERN => {pattern}" if pattern else "" 1991 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1992 1993 ordinality = expression.args.get("ordinality") or "" 1994 if ordinality: 1995 ordinality = f" WITH ORDINALITY{alias}" 1996 alias = "" 1997 1998 when = self.sql(expression, "when") 1999 if when: 2000 table = f"{table} {when}" 2001 2002 changes = self.sql(expression, "changes") 2003 changes = f" {changes}" if changes else "" 2004 2005 rows_from = self.expressions(expression, key="rows_from") 2006 if rows_from: 2007 table = f"ROWS FROM {self.wrap(rows_from)}" 2008 2009 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2011 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2012 table = self.func("TABLE", expression.this) 2013 alias = self.sql(expression, "alias") 2014 alias = f" AS {alias}" if alias else "" 2015 sample = self.sql(expression, "sample") 2016 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2017 joins = self.indent( 2018 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2019 ) 2020 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2022 def tablesample_sql( 2023 self, 2024 expression: exp.TableSample, 2025 tablesample_keyword: t.Optional[str] = None, 2026 ) -> str: 2027 method = self.sql(expression, "method") 2028 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2029 numerator = self.sql(expression, "bucket_numerator") 2030 denominator = self.sql(expression, "bucket_denominator") 2031 field = self.sql(expression, "bucket_field") 2032 field = f" ON {field}" if field else "" 2033 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2034 seed = self.sql(expression, "seed") 2035 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2036 2037 size = self.sql(expression, "size") 2038 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2039 size = f"{size} ROWS" 2040 2041 percent = self.sql(expression, "percent") 2042 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2043 percent = f"{percent} PERCENT" 2044 2045 expr = f"{bucket}{percent}{size}" 2046 if self.TABLESAMPLE_REQUIRES_PARENS: 2047 expr = f"({expr})" 2048 2049 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2051 def pivot_sql(self, expression: exp.Pivot) -> str: 2052 expressions = self.expressions(expression, flat=True) 2053 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2054 2055 group = self.sql(expression, "group") 2056 2057 if expression.this: 2058 this = self.sql(expression, "this") 2059 if not expressions: 2060 return f"UNPIVOT {this}" 2061 2062 on = f"{self.seg('ON')} {expressions}" 2063 into = self.sql(expression, "into") 2064 into = f"{self.seg('INTO')} {into}" if into else "" 2065 using = self.expressions(expression, key="using", flat=True) 2066 using = f"{self.seg('USING')} {using}" if using else "" 2067 return f"{direction} {this}{on}{into}{using}{group}" 2068 2069 alias = self.sql(expression, "alias") 2070 alias = f" AS {alias}" if alias else "" 2071 2072 fields = self.expressions( 2073 expression, 2074 "fields", 2075 sep=" ", 2076 dynamic=True, 2077 new_line=True, 2078 skip_first=True, 2079 skip_last=True, 2080 ) 2081 2082 include_nulls = expression.args.get("include_nulls") 2083 if include_nulls is not None: 2084 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2085 else: 2086 nulls = "" 2087 2088 default_on_null = self.sql(expression, "default_on_null") 2089 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2090 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2101 def update_sql(self, expression: exp.Update) -> str: 2102 this = self.sql(expression, "this") 2103 set_sql = self.expressions(expression, flat=True) 2104 from_sql = self.sql(expression, "from") 2105 where_sql = self.sql(expression, "where") 2106 returning = self.sql(expression, "returning") 2107 order = self.sql(expression, "order") 2108 limit = self.sql(expression, "limit") 2109 if self.RETURNING_END: 2110 expression_sql = f"{from_sql}{where_sql}{returning}" 2111 else: 2112 expression_sql = f"{returning}{from_sql}{where_sql}" 2113 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2114 return self.prepend_ctes(expression, sql)
2116 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2117 values_as_table = values_as_table and self.VALUES_AS_TABLE 2118 2119 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2120 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2121 args = self.expressions(expression) 2122 alias = self.sql(expression, "alias") 2123 values = f"VALUES{self.seg('')}{args}" 2124 values = ( 2125 f"({values})" 2126 if self.WRAP_DERIVED_VALUES 2127 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2128 else values 2129 ) 2130 return f"{values} AS {alias}" if alias else values 2131 2132 # Converts `VALUES...` expression into a series of select unions. 2133 alias_node = expression.args.get("alias") 2134 column_names = alias_node and alias_node.columns 2135 2136 selects: t.List[exp.Query] = [] 2137 2138 for i, tup in enumerate(expression.expressions): 2139 row = tup.expressions 2140 2141 if i == 0 and column_names: 2142 row = [ 2143 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2144 ] 2145 2146 selects.append(exp.Select(expressions=row)) 2147 2148 if self.pretty: 2149 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2150 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2151 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2152 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2153 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2154 2155 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2156 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2157 return f"({unions}){alias}"
2162 @unsupported_args("expressions") 2163 def into_sql(self, expression: exp.Into) -> str: 2164 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2165 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2166 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2183 def group_sql(self, expression: exp.Group) -> str: 2184 group_by_all = expression.args.get("all") 2185 if group_by_all is True: 2186 modifier = " ALL" 2187 elif group_by_all is False: 2188 modifier = " DISTINCT" 2189 else: 2190 modifier = "" 2191 2192 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2193 2194 grouping_sets = self.expressions(expression, key="grouping_sets") 2195 cube = self.expressions(expression, key="cube") 2196 rollup = self.expressions(expression, key="rollup") 2197 2198 groupings = csv( 2199 self.seg(grouping_sets) if grouping_sets else "", 2200 self.seg(cube) if cube else "", 2201 self.seg(rollup) if rollup else "", 2202 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2203 sep=self.GROUPINGS_SEP, 2204 ) 2205 2206 if ( 2207 expression.expressions 2208 and groupings 2209 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2210 ): 2211 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2212 2213 return f"{group_by}{groupings}"
2219 def connect_sql(self, expression: exp.Connect) -> str: 2220 start = self.sql(expression, "start") 2221 start = self.seg(f"START WITH {start}") if start else "" 2222 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2223 connect = self.sql(expression, "connect") 2224 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2225 return start + connect
2230 def join_sql(self, expression: exp.Join) -> str: 2231 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2232 side = None 2233 else: 2234 side = expression.side 2235 2236 op_sql = " ".join( 2237 op 2238 for op in ( 2239 expression.method, 2240 "GLOBAL" if expression.args.get("global") else None, 2241 side, 2242 expression.kind, 2243 expression.hint if self.JOIN_HINTS else None, 2244 ) 2245 if op 2246 ) 2247 match_cond = self.sql(expression, "match_condition") 2248 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2249 on_sql = self.sql(expression, "on") 2250 using = expression.args.get("using") 2251 2252 if not on_sql and using: 2253 on_sql = csv(*(self.sql(column) for column in using)) 2254 2255 this = expression.this 2256 this_sql = self.sql(this) 2257 2258 exprs = self.expressions(expression) 2259 if exprs: 2260 this_sql = f"{this_sql},{self.seg(exprs)}" 2261 2262 if on_sql: 2263 on_sql = self.indent(on_sql, skip_first=True) 2264 space = self.seg(" " * self.pad) if self.pretty else " " 2265 if using: 2266 on_sql = f"{space}USING ({on_sql})" 2267 else: 2268 on_sql = f"{space}ON {on_sql}" 2269 elif not op_sql: 2270 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2271 return f" {this_sql}" 2272 2273 return f", {this_sql}" 2274 2275 if op_sql != "STRAIGHT_JOIN": 2276 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2277 2278 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}"
2285 def lateral_op(self, expression: exp.Lateral) -> str: 2286 cross_apply = expression.args.get("cross_apply") 2287 2288 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2289 if cross_apply is True: 2290 op = "INNER JOIN " 2291 elif cross_apply is False: 2292 op = "LEFT JOIN " 2293 else: 2294 op = "" 2295 2296 return f"{op}LATERAL"
2298 def lateral_sql(self, expression: exp.Lateral) -> str: 2299 this = self.sql(expression, "this") 2300 2301 if expression.args.get("view"): 2302 alias = expression.args["alias"] 2303 columns = self.expressions(alias, key="columns", flat=True) 2304 table = f" {alias.name}" if alias.name else "" 2305 columns = f" AS {columns}" if columns else "" 2306 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2307 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2308 2309 alias = self.sql(expression, "alias") 2310 alias = f" AS {alias}" if alias else "" 2311 2312 ordinality = expression.args.get("ordinality") or "" 2313 if ordinality: 2314 ordinality = f" WITH ORDINALITY{alias}" 2315 alias = "" 2316 2317 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2319 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2320 this = self.sql(expression, "this") 2321 2322 args = [ 2323 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2324 for e in (expression.args.get(k) for k in ("offset", "expression")) 2325 if e 2326 ] 2327 2328 args_sql = ", ".join(self.sql(e) for e in args) 2329 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2330 expressions = self.expressions(expression, flat=True) 2331 limit_options = self.sql(expression, "limit_options") 2332 expressions = f" BY {expressions}" if expressions else "" 2333 2334 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2336 def offset_sql(self, expression: exp.Offset) -> str: 2337 this = self.sql(expression, "this") 2338 value = expression.expression 2339 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2340 expressions = self.expressions(expression, flat=True) 2341 expressions = f" BY {expressions}" if expressions else "" 2342 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2344 def setitem_sql(self, expression: exp.SetItem) -> str: 2345 kind = self.sql(expression, "kind") 2346 kind = f"{kind} " if kind else "" 2347 this = self.sql(expression, "this") 2348 expressions = self.expressions(expression) 2349 collate = self.sql(expression, "collate") 2350 collate = f" COLLATE {collate}" if collate else "" 2351 global_ = "GLOBAL " if expression.args.get("global") else "" 2352 return f"{global_}{kind}{this}{expressions}{collate}"
2362 def lock_sql(self, expression: exp.Lock) -> str: 2363 if not self.LOCKING_READS_SUPPORTED: 2364 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2365 return "" 2366 2367 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2368 expressions = self.expressions(expression, flat=True) 2369 expressions = f" OF {expressions}" if expressions else "" 2370 wait = expression.args.get("wait") 2371 2372 if wait is not None: 2373 if isinstance(wait, exp.Literal): 2374 wait = f" WAIT {self.sql(wait)}" 2375 else: 2376 wait = " NOWAIT" if wait else " SKIP LOCKED" 2377 2378 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2386 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2387 if self.dialect.ESCAPED_SEQUENCES: 2388 to_escaped = self.dialect.ESCAPED_SEQUENCES 2389 text = "".join( 2390 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2391 ) 2392 2393 return self._replace_line_breaks(text).replace( 2394 self.dialect.QUOTE_END, self._escaped_quote_end 2395 )
2397 def loaddata_sql(self, expression: exp.LoadData) -> str: 2398 local = " LOCAL" if expression.args.get("local") else "" 2399 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2400 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2401 this = f" INTO TABLE {self.sql(expression, 'this')}" 2402 partition = self.sql(expression, "partition") 2403 partition = f" {partition}" if partition else "" 2404 input_format = self.sql(expression, "input_format") 2405 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2406 serde = self.sql(expression, "serde") 2407 serde = f" SERDE {serde}" if serde else "" 2408 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2416 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2417 this = self.sql(expression, "this") 2418 this = f"{this} " if this else this 2419 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2420 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2422 def withfill_sql(self, expression: exp.WithFill) -> str: 2423 from_sql = self.sql(expression, "from") 2424 from_sql = f" FROM {from_sql}" if from_sql else "" 2425 to_sql = self.sql(expression, "to") 2426 to_sql = f" TO {to_sql}" if to_sql else "" 2427 step_sql = self.sql(expression, "step") 2428 step_sql = f" STEP {step_sql}" if step_sql else "" 2429 interpolated_values = [ 2430 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2431 if isinstance(e, exp.Alias) 2432 else self.sql(e, "this") 2433 for e in expression.args.get("interpolate") or [] 2434 ] 2435 interpolate = ( 2436 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2437 ) 2438 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2449 def ordered_sql(self, expression: exp.Ordered) -> str: 2450 desc = expression.args.get("desc") 2451 asc = not desc 2452 2453 nulls_first = expression.args.get("nulls_first") 2454 nulls_last = not nulls_first 2455 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2456 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2457 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2458 2459 this = self.sql(expression, "this") 2460 2461 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2462 nulls_sort_change = "" 2463 if nulls_first and ( 2464 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2465 ): 2466 nulls_sort_change = " NULLS FIRST" 2467 elif ( 2468 nulls_last 2469 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2470 and not nulls_are_last 2471 ): 2472 nulls_sort_change = " NULLS LAST" 2473 2474 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2475 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2476 window = expression.find_ancestor(exp.Window, exp.Select) 2477 if isinstance(window, exp.Window) and window.args.get("spec"): 2478 self.unsupported( 2479 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2480 ) 2481 nulls_sort_change = "" 2482 elif self.NULL_ORDERING_SUPPORTED is False and ( 2483 (asc and nulls_sort_change == " NULLS LAST") 2484 or (desc and nulls_sort_change == " NULLS FIRST") 2485 ): 2486 # BigQuery does not allow these ordering/nulls combinations when used under 2487 # an aggregation func or under a window containing one 2488 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2489 2490 if isinstance(ancestor, exp.Window): 2491 ancestor = ancestor.this 2492 if isinstance(ancestor, exp.AggFunc): 2493 self.unsupported( 2494 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2495 ) 2496 nulls_sort_change = "" 2497 elif self.NULL_ORDERING_SUPPORTED is None: 2498 if expression.this.is_int: 2499 self.unsupported( 2500 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2501 ) 2502 elif not isinstance(expression.this, exp.Rand): 2503 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2504 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2505 nulls_sort_change = "" 2506 2507 with_fill = self.sql(expression, "with_fill") 2508 with_fill = f" {with_fill}" if with_fill else "" 2509 2510 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2520 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2521 partition = self.partition_by_sql(expression) 2522 order = self.sql(expression, "order") 2523 measures = self.expressions(expression, key="measures") 2524 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2525 rows = self.sql(expression, "rows") 2526 rows = self.seg(rows) if rows else "" 2527 after = self.sql(expression, "after") 2528 after = self.seg(after) if after else "" 2529 pattern = self.sql(expression, "pattern") 2530 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2531 definition_sqls = [ 2532 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2533 for definition in expression.args.get("define", []) 2534 ] 2535 definitions = self.expressions(sqls=definition_sqls) 2536 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2537 body = "".join( 2538 ( 2539 partition, 2540 order, 2541 measures, 2542 rows, 2543 after, 2544 pattern, 2545 define, 2546 ) 2547 ) 2548 alias = self.sql(expression, "alias") 2549 alias = f" {alias}" if alias else "" 2550 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2552 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2553 limit = expression.args.get("limit") 2554 2555 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2556 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2557 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2558 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2559 2560 return csv( 2561 *sqls, 2562 *[self.sql(join) for join in expression.args.get("joins") or []], 2563 self.sql(expression, "match"), 2564 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2565 self.sql(expression, "prewhere"), 2566 self.sql(expression, "where"), 2567 self.sql(expression, "connect"), 2568 self.sql(expression, "group"), 2569 self.sql(expression, "having"), 2570 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2571 self.sql(expression, "order"), 2572 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2573 *self.after_limit_modifiers(expression), 2574 self.options_modifier(expression), 2575 sep="", 2576 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2585 def offset_limit_modifiers( 2586 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2587 ) -> t.List[str]: 2588 return [ 2589 self.sql(expression, "offset") if fetch else self.sql(limit), 2590 self.sql(limit) if fetch else self.sql(expression, "offset"), 2591 ]
2598 def select_sql(self, expression: exp.Select) -> str: 2599 into = expression.args.get("into") 2600 if not self.SUPPORTS_SELECT_INTO and into: 2601 into.pop() 2602 2603 hint = self.sql(expression, "hint") 2604 distinct = self.sql(expression, "distinct") 2605 distinct = f" {distinct}" if distinct else "" 2606 kind = self.sql(expression, "kind") 2607 2608 limit = expression.args.get("limit") 2609 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2610 top = self.limit_sql(limit, top=True) 2611 limit.pop() 2612 else: 2613 top = "" 2614 2615 expressions = self.expressions(expression) 2616 2617 if kind: 2618 if kind in self.SELECT_KINDS: 2619 kind = f" AS {kind}" 2620 else: 2621 if kind == "STRUCT": 2622 expressions = self.expressions( 2623 sqls=[ 2624 self.sql( 2625 exp.Struct( 2626 expressions=[ 2627 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2628 if isinstance(e, exp.Alias) 2629 else e 2630 for e in expression.expressions 2631 ] 2632 ) 2633 ) 2634 ] 2635 ) 2636 kind = "" 2637 2638 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2639 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2640 2641 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2642 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2643 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2644 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2645 sql = self.query_modifiers( 2646 expression, 2647 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2648 self.sql(expression, "into", comment=False), 2649 self.sql(expression, "from", comment=False), 2650 ) 2651 2652 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2653 if expression.args.get("with"): 2654 sql = self.maybe_comment(sql, expression) 2655 expression.pop_comments() 2656 2657 sql = self.prepend_ctes(expression, sql) 2658 2659 if not self.SUPPORTS_SELECT_INTO and into: 2660 if into.args.get("temporary"): 2661 table_kind = " TEMPORARY" 2662 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2663 table_kind = " UNLOGGED" 2664 else: 2665 table_kind = "" 2666 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2667 2668 return sql
2680 def star_sql(self, expression: exp.Star) -> str: 2681 except_ = self.expressions(expression, key="except", flat=True) 2682 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2683 replace = self.expressions(expression, key="replace", flat=True) 2684 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2685 rename = self.expressions(expression, key="rename", flat=True) 2686 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2687 return f"*{except_}{replace}{rename}"
2703 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2704 alias = self.sql(expression, "alias") 2705 alias = f"{sep}{alias}" if alias else "" 2706 sample = self.sql(expression, "sample") 2707 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2708 alias = f"{sample}{alias}" 2709 2710 # Set to None so it's not generated again by self.query_modifiers() 2711 expression.set("sample", None) 2712 2713 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2714 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2715 return self.prepend_ctes(expression, sql)
2721 def unnest_sql(self, expression: exp.Unnest) -> str: 2722 args = self.expressions(expression, flat=True) 2723 2724 alias = expression.args.get("alias") 2725 offset = expression.args.get("offset") 2726 2727 if self.UNNEST_WITH_ORDINALITY: 2728 if alias and isinstance(offset, exp.Expression): 2729 alias.append("columns", offset) 2730 2731 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2732 columns = alias.columns 2733 alias = self.sql(columns[0]) if columns else "" 2734 else: 2735 alias = self.sql(alias) 2736 2737 alias = f" AS {alias}" if alias else alias 2738 if self.UNNEST_WITH_ORDINALITY: 2739 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2740 else: 2741 if isinstance(offset, exp.Expression): 2742 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2743 elif offset: 2744 suffix = f"{alias} WITH OFFSET" 2745 else: 2746 suffix = alias 2747 2748 return f"UNNEST({args}){suffix}"
2757 def window_sql(self, expression: exp.Window) -> str: 2758 this = self.sql(expression, "this") 2759 partition = self.partition_by_sql(expression) 2760 order = expression.args.get("order") 2761 order = self.order_sql(order, flat=True) if order else "" 2762 spec = self.sql(expression, "spec") 2763 alias = self.sql(expression, "alias") 2764 over = self.sql(expression, "over") or "OVER" 2765 2766 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2767 2768 first = expression.args.get("first") 2769 if first is None: 2770 first = "" 2771 else: 2772 first = "FIRST" if first else "LAST" 2773 2774 if not partition and not order and not spec and alias: 2775 return f"{this} {alias}" 2776 2777 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2778 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2784 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2785 kind = self.sql(expression, "kind") 2786 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2787 end = ( 2788 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2789 or "CURRENT ROW" 2790 ) 2791 return f"{kind} BETWEEN {start} AND {end}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2804 def bracket_offset_expressions( 2805 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2806 ) -> t.List[exp.Expression]: 2807 return apply_index_offset( 2808 expression.this, 2809 expression.expressions, 2810 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2811 dialect=self.dialect, 2812 )
2822 def any_sql(self, expression: exp.Any) -> str: 2823 this = self.sql(expression, "this") 2824 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2825 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2826 this = self.wrap(this) 2827 return f"ANY{this}" 2828 return f"ANY {this}"
2833 def case_sql(self, expression: exp.Case) -> str: 2834 this = self.sql(expression, "this") 2835 statements = [f"CASE {this}" if this else "CASE"] 2836 2837 for e in expression.args["ifs"]: 2838 statements.append(f"WHEN {self.sql(e, 'this')}") 2839 statements.append(f"THEN {self.sql(e, 'true')}") 2840 2841 default = self.sql(expression, "default") 2842 2843 if default: 2844 statements.append(f"ELSE {default}") 2845 2846 statements.append("END") 2847 2848 if self.pretty and self.too_wide(statements): 2849 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2850 2851 return " ".join(statements)
2868 def trim_sql(self, expression: exp.Trim) -> str: 2869 trim_type = self.sql(expression, "position") 2870 2871 if trim_type == "LEADING": 2872 func_name = "LTRIM" 2873 elif trim_type == "TRAILING": 2874 func_name = "RTRIM" 2875 else: 2876 func_name = "TRIM" 2877 2878 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
2880 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2881 args = expression.expressions 2882 if isinstance(expression, exp.ConcatWs): 2883 args = args[1:] # Skip the delimiter 2884 2885 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2886 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2887 2888 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2889 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2890 2891 return args
2893 def concat_sql(self, expression: exp.Concat) -> str: 2894 expressions = self.convert_concat_args(expression) 2895 2896 # Some dialects don't allow a single-argument CONCAT call 2897 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2898 return self.sql(expressions[0]) 2899 2900 return self.func("CONCAT", *expressions)
2911 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2912 expressions = self.expressions(expression, flat=True) 2913 expressions = f" ({expressions})" if expressions else "" 2914 reference = self.sql(expression, "reference") 2915 reference = f" {reference}" if reference else "" 2916 delete = self.sql(expression, "delete") 2917 delete = f" ON DELETE {delete}" if delete else "" 2918 update = self.sql(expression, "update") 2919 update = f" ON UPDATE {update}" if update else "" 2920 return f"FOREIGN KEY{expressions}{reference}{delete}{update}"
2922 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2923 expressions = self.expressions(expression, flat=True) 2924 options = self.expressions(expression, key="options", flat=True, sep=" ") 2925 options = f" {options}" if options else "" 2926 return f"PRIMARY KEY ({expressions}){options}"
2939 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2940 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2941 2942 if expression.args.get("escape"): 2943 path = self.escape_str(path) 2944 2945 if self.QUOTE_JSON_PATH: 2946 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2947 2948 return path
2950 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2951 if isinstance(expression, exp.JSONPathPart): 2952 transform = self.TRANSFORMS.get(expression.__class__) 2953 if not callable(transform): 2954 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2955 return "" 2956 2957 return transform(self, expression) 2958 2959 if isinstance(expression, int): 2960 return str(expression) 2961 2962 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2963 escaped = expression.replace("'", "\\'") 2964 escaped = f"\\'{expression}\\'" 2965 else: 2966 escaped = expression.replace('"', '\\"') 2967 escaped = f'"{escaped}"' 2968 2969 return escaped
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
2974 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2975 null_handling = expression.args.get("null_handling") 2976 null_handling = f" {null_handling}" if null_handling else "" 2977 2978 unique_keys = expression.args.get("unique_keys") 2979 if unique_keys is not None: 2980 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2981 else: 2982 unique_keys = "" 2983 2984 return_type = self.sql(expression, "return_type") 2985 return_type = f" RETURNING {return_type}" if return_type else "" 2986 encoding = self.sql(expression, "encoding") 2987 encoding = f" ENCODING {encoding}" if encoding else "" 2988 2989 return self.func( 2990 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2991 *expression.expressions, 2992 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2993 )
2998 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2999 null_handling = expression.args.get("null_handling") 3000 null_handling = f" {null_handling}" if null_handling else "" 3001 return_type = self.sql(expression, "return_type") 3002 return_type = f" RETURNING {return_type}" if return_type else "" 3003 strict = " STRICT" if expression.args.get("strict") else "" 3004 return self.func( 3005 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3006 )
3008 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3009 this = self.sql(expression, "this") 3010 order = self.sql(expression, "order") 3011 null_handling = expression.args.get("null_handling") 3012 null_handling = f" {null_handling}" if null_handling else "" 3013 return_type = self.sql(expression, "return_type") 3014 return_type = f" RETURNING {return_type}" if return_type else "" 3015 strict = " STRICT" if expression.args.get("strict") else "" 3016 return self.func( 3017 "JSON_ARRAYAGG", 3018 this, 3019 suffix=f"{order}{null_handling}{return_type}{strict})", 3020 )
3022 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3023 path = self.sql(expression, "path") 3024 path = f" PATH {path}" if path else "" 3025 nested_schema = self.sql(expression, "nested_schema") 3026 3027 if nested_schema: 3028 return f"NESTED{path} {nested_schema}" 3029 3030 this = self.sql(expression, "this") 3031 kind = self.sql(expression, "kind") 3032 kind = f" {kind}" if kind else "" 3033 return f"{this}{kind}{path}"
3038 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3039 this = self.sql(expression, "this") 3040 path = self.sql(expression, "path") 3041 path = f", {path}" if path else "" 3042 error_handling = expression.args.get("error_handling") 3043 error_handling = f" {error_handling}" if error_handling else "" 3044 empty_handling = expression.args.get("empty_handling") 3045 empty_handling = f" {empty_handling}" if empty_handling else "" 3046 schema = self.sql(expression, "schema") 3047 return self.func( 3048 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3049 )
3051 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3052 this = self.sql(expression, "this") 3053 kind = self.sql(expression, "kind") 3054 path = self.sql(expression, "path") 3055 path = f" {path}" if path else "" 3056 as_json = " AS JSON" if expression.args.get("as_json") else "" 3057 return f"{this} {kind}{path}{as_json}"
3059 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3060 this = self.sql(expression, "this") 3061 path = self.sql(expression, "path") 3062 path = f", {path}" if path else "" 3063 expressions = self.expressions(expression) 3064 with_ = ( 3065 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3066 if expressions 3067 else "" 3068 ) 3069 return f"OPENJSON({this}{path}){with_}"
3071 def in_sql(self, expression: exp.In) -> str: 3072 query = expression.args.get("query") 3073 unnest = expression.args.get("unnest") 3074 field = expression.args.get("field") 3075 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3076 3077 if query: 3078 in_sql = self.sql(query) 3079 elif unnest: 3080 in_sql = self.in_unnest_op(unnest) 3081 elif field: 3082 in_sql = self.sql(field) 3083 else: 3084 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3085 3086 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3091 def interval_sql(self, expression: exp.Interval) -> str: 3092 unit = self.sql(expression, "unit") 3093 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3094 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3095 unit = f" {unit}" if unit else "" 3096 3097 if self.SINGLE_STRING_INTERVAL: 3098 this = expression.this.name if expression.this else "" 3099 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3100 3101 this = self.sql(expression, "this") 3102 if this: 3103 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3104 this = f" {this}" if unwrapped else f" ({this})" 3105 3106 return f"INTERVAL{this}{unit}"
3111 def reference_sql(self, expression: exp.Reference) -> str: 3112 this = self.sql(expression, "this") 3113 expressions = self.expressions(expression, flat=True) 3114 expressions = f"({expressions})" if expressions else "" 3115 options = self.expressions(expression, key="options", flat=True, sep=" ") 3116 options = f" {options}" if options else "" 3117 return f"REFERENCES {this}{expressions}{options}"
3119 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3120 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3121 parent = expression.parent 3122 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3123 return self.func( 3124 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3125 )
3145 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3146 alias = expression.args["alias"] 3147 3148 parent = expression.parent 3149 pivot = parent and parent.parent 3150 3151 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3152 identifier_alias = isinstance(alias, exp.Identifier) 3153 literal_alias = isinstance(alias, exp.Literal) 3154 3155 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3156 alias.replace(exp.Literal.string(alias.output_name)) 3157 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3158 alias.replace(exp.to_identifier(alias.output_name)) 3159 3160 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3198 def connector_sql( 3199 self, 3200 expression: exp.Connector, 3201 op: str, 3202 stack: t.Optional[t.List[str | exp.Expression]] = None, 3203 ) -> str: 3204 if stack is not None: 3205 if expression.expressions: 3206 stack.append(self.expressions(expression, sep=f" {op} ")) 3207 else: 3208 stack.append(expression.right) 3209 if expression.comments and self.comments: 3210 for comment in expression.comments: 3211 if comment: 3212 op += f" /*{self.pad_comment(comment)}*/" 3213 stack.extend((op, expression.left)) 3214 return op 3215 3216 stack = [expression] 3217 sqls: t.List[str] = [] 3218 ops = set() 3219 3220 while stack: 3221 node = stack.pop() 3222 if isinstance(node, exp.Connector): 3223 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3224 else: 3225 sql = self.sql(node) 3226 if sqls and sqls[-1] in ops: 3227 sqls[-1] += f" {sql}" 3228 else: 3229 sqls.append(sql) 3230 3231 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3232 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3252 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3253 format_sql = self.sql(expression, "format") 3254 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3255 to_sql = self.sql(expression, "to") 3256 to_sql = f" {to_sql}" if to_sql else "" 3257 action = self.sql(expression, "action") 3258 action = f" {action}" if action else "" 3259 default = self.sql(expression, "default") 3260 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3261 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3275 def comment_sql(self, expression: exp.Comment) -> str: 3276 this = self.sql(expression, "this") 3277 kind = expression.args["kind"] 3278 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3279 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3280 expression_sql = self.sql(expression, "expression") 3281 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3283 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3284 this = self.sql(expression, "this") 3285 delete = " DELETE" if expression.args.get("delete") else "" 3286 recompress = self.sql(expression, "recompress") 3287 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3288 to_disk = self.sql(expression, "to_disk") 3289 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3290 to_volume = self.sql(expression, "to_volume") 3291 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3292 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3294 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3295 where = self.sql(expression, "where") 3296 group = self.sql(expression, "group") 3297 aggregates = self.expressions(expression, key="aggregates") 3298 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3299 3300 if not (where or group or aggregates) and len(expression.expressions) == 1: 3301 return f"TTL {self.expressions(expression, flat=True)}" 3302 3303 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3320 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3321 this = self.sql(expression, "this") 3322 3323 dtype = self.sql(expression, "dtype") 3324 if dtype: 3325 collate = self.sql(expression, "collate") 3326 collate = f" COLLATE {collate}" if collate else "" 3327 using = self.sql(expression, "using") 3328 using = f" USING {using}" if using else "" 3329 return f"ALTER COLUMN {this} {self.ALTER_SET_TYPE} {dtype}{collate}{using}" 3330 3331 default = self.sql(expression, "default") 3332 if default: 3333 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3334 3335 comment = self.sql(expression, "comment") 3336 if comment: 3337 return f"ALTER COLUMN {this} COMMENT {comment}" 3338 3339 visible = expression.args.get("visible") 3340 if visible: 3341 return f"ALTER COLUMN {this} SET {visible}" 3342 3343 allow_null = expression.args.get("allow_null") 3344 drop = expression.args.get("drop") 3345 3346 if not drop and not allow_null: 3347 self.unsupported("Unsupported ALTER COLUMN syntax") 3348 3349 if allow_null is not None: 3350 keyword = "DROP" if drop else "SET" 3351 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3352 3353 return f"ALTER COLUMN {this} DROP DEFAULT"
3369 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3370 compound = " COMPOUND" if expression.args.get("compound") else "" 3371 this = self.sql(expression, "this") 3372 expressions = self.expressions(expression, flat=True) 3373 expressions = f"({expressions})" if expressions else "" 3374 return f"ALTER{compound} SORTKEY {this or expressions}"
3376 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3377 if not self.RENAME_TABLE_WITH_DB: 3378 # Remove db from tables 3379 expression = expression.transform( 3380 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3381 ).assert_is(exp.AlterRename) 3382 this = self.sql(expression, "this") 3383 return f"RENAME TO {this}"
3395 def alter_sql(self, expression: exp.Alter) -> str: 3396 actions = expression.args["actions"] 3397 3398 if isinstance(actions[0], exp.ColumnDef): 3399 actions = self.add_column_sql(expression) 3400 elif isinstance(actions[0], exp.Schema): 3401 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3402 elif isinstance(actions[0], exp.Delete): 3403 actions = self.expressions(expression, key="actions", flat=True) 3404 elif isinstance(actions[0], exp.Query): 3405 actions = "AS " + self.expressions(expression, key="actions") 3406 else: 3407 actions = self.expressions(expression, key="actions", flat=True) 3408 3409 exists = " IF EXISTS" if expression.args.get("exists") else "" 3410 on_cluster = self.sql(expression, "cluster") 3411 on_cluster = f" {on_cluster}" if on_cluster else "" 3412 only = " ONLY" if expression.args.get("only") else "" 3413 options = self.expressions(expression, key="options") 3414 options = f", {options}" if options else "" 3415 kind = self.sql(expression, "kind") 3416 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3417 3418 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}"
3420 def add_column_sql(self, expression: exp.Alter) -> str: 3421 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3422 return self.expressions( 3423 expression, 3424 key="actions", 3425 prefix="ADD COLUMN ", 3426 skip_first=True, 3427 ) 3428 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
3438 def distinct_sql(self, expression: exp.Distinct) -> str: 3439 this = self.expressions(expression, flat=True) 3440 3441 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3442 case = exp.case() 3443 for arg in expression.expressions: 3444 case = case.when(arg.is_(exp.null()), exp.null()) 3445 this = self.sql(case.else_(f"({this})")) 3446 3447 this = f" {this}" if this else "" 3448 3449 on = self.sql(expression, "on") 3450 on = f" ON {on}" if on else "" 3451 return f"DISTINCT{this}{on}"
3480 def div_sql(self, expression: exp.Div) -> str: 3481 l, r = expression.left, expression.right 3482 3483 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3484 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3485 3486 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3487 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3488 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3489 3490 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3491 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3492 return self.sql( 3493 exp.cast( 3494 l / r, 3495 to=exp.DataType.Type.BIGINT, 3496 ) 3497 ) 3498 3499 return self.binary(expression, "/")
3595 def log_sql(self, expression: exp.Log) -> str: 3596 this = expression.this 3597 expr = expression.expression 3598 3599 if self.dialect.LOG_BASE_FIRST is False: 3600 this, expr = expr, this 3601 elif self.dialect.LOG_BASE_FIRST is None and expr: 3602 if this.name in ("2", "10"): 3603 return self.func(f"LOG{this.name}", expr) 3604 3605 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3606 3607 return self.func("LOG", this, expr)
3616 def binary(self, expression: exp.Binary, op: str) -> str: 3617 sqls: t.List[str] = [] 3618 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3619 binary_type = type(expression) 3620 3621 while stack: 3622 node = stack.pop() 3623 3624 if type(node) is binary_type: 3625 op_func = node.args.get("operator") 3626 if op_func: 3627 op = f"OPERATOR({self.sql(op_func)})" 3628 3629 stack.append(node.right) 3630 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3631 stack.append(node.left) 3632 else: 3633 sqls.append(self.sql(node)) 3634 3635 return "".join(sqls)
3644 def function_fallback_sql(self, expression: exp.Func) -> str: 3645 args = [] 3646 3647 for key in expression.arg_types: 3648 arg_value = expression.args.get(key) 3649 3650 if isinstance(arg_value, list): 3651 for value in arg_value: 3652 args.append(value) 3653 elif arg_value is not None: 3654 args.append(arg_value) 3655 3656 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3657 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3658 else: 3659 name = expression.sql_name() 3660 3661 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3663 def func( 3664 self, 3665 name: str, 3666 *args: t.Optional[exp.Expression | str], 3667 prefix: str = "(", 3668 suffix: str = ")", 3669 normalize: bool = True, 3670 ) -> str: 3671 name = self.normalize_func(name) if normalize else name 3672 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3674 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3675 arg_sqls = tuple( 3676 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3677 ) 3678 if self.pretty and self.too_wide(arg_sqls): 3679 return self.indent( 3680 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3681 ) 3682 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3687 def format_time( 3688 self, 3689 expression: exp.Expression, 3690 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3691 inverse_time_trie: t.Optional[t.Dict] = None, 3692 ) -> t.Optional[str]: 3693 return format_time( 3694 self.sql(expression, "format"), 3695 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3696 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3697 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3699 def expressions( 3700 self, 3701 expression: t.Optional[exp.Expression] = None, 3702 key: t.Optional[str] = None, 3703 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3704 flat: bool = False, 3705 indent: bool = True, 3706 skip_first: bool = False, 3707 skip_last: bool = False, 3708 sep: str = ", ", 3709 prefix: str = "", 3710 dynamic: bool = False, 3711 new_line: bool = False, 3712 ) -> str: 3713 expressions = expression.args.get(key or "expressions") if expression else sqls 3714 3715 if not expressions: 3716 return "" 3717 3718 if flat: 3719 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3720 3721 num_sqls = len(expressions) 3722 result_sqls = [] 3723 3724 for i, e in enumerate(expressions): 3725 sql = self.sql(e, comment=False) 3726 if not sql: 3727 continue 3728 3729 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3730 3731 if self.pretty: 3732 if self.leading_comma: 3733 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3734 else: 3735 result_sqls.append( 3736 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3737 ) 3738 else: 3739 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3740 3741 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3742 if new_line: 3743 result_sqls.insert(0, "") 3744 result_sqls.append("") 3745 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3746 else: 3747 result_sql = "".join(result_sqls) 3748 3749 return ( 3750 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3751 if indent 3752 else result_sql 3753 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3755 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3756 flat = flat or isinstance(expression.parent, exp.Properties) 3757 expressions_sql = self.expressions(expression, flat=flat) 3758 if flat: 3759 return f"{op} {expressions_sql}" 3760 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3762 def naked_property(self, expression: exp.Property) -> str: 3763 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3764 if not property_name: 3765 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3766 return f"{property_name} {self.sql(expression, 'this')}"
3774 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3775 this = self.sql(expression, "this") 3776 expressions = self.no_identify(self.expressions, expression) 3777 expressions = ( 3778 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3779 ) 3780 return f"{this}{expressions}" if expressions.strip() != "" else this
3790 def when_sql(self, expression: exp.When) -> str: 3791 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3792 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3793 condition = self.sql(expression, "condition") 3794 condition = f" AND {condition}" if condition else "" 3795 3796 then_expression = expression.args.get("then") 3797 if isinstance(then_expression, exp.Insert): 3798 this = self.sql(then_expression, "this") 3799 this = f"INSERT {this}" if this else "INSERT" 3800 then = self.sql(then_expression, "expression") 3801 then = f"{this} VALUES {then}" if then else this 3802 elif isinstance(then_expression, exp.Update): 3803 if isinstance(then_expression.args.get("expressions"), exp.Star): 3804 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3805 else: 3806 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3807 else: 3808 then = self.sql(then_expression) 3809 return f"WHEN {matched}{source}{condition} THEN {then}"
3814 def merge_sql(self, expression: exp.Merge) -> str: 3815 table = expression.this 3816 table_alias = "" 3817 3818 hints = table.args.get("hints") 3819 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3820 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3821 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3822 3823 this = self.sql(table) 3824 using = f"USING {self.sql(expression, 'using')}" 3825 on = f"ON {self.sql(expression, 'on')}" 3826 whens = self.sql(expression, "whens") 3827 3828 returning = self.sql(expression, "returning") 3829 if returning: 3830 whens = f"{whens}{returning}" 3831 3832 sep = self.sep() 3833 3834 return self.prepend_ctes( 3835 expression, 3836 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3837 )
3843 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3844 if not self.SUPPORTS_TO_NUMBER: 3845 self.unsupported("Unsupported TO_NUMBER function") 3846 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3847 3848 fmt = expression.args.get("format") 3849 if not fmt: 3850 self.unsupported("Conversion format is required for TO_NUMBER") 3851 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3852 3853 return self.func("TO_NUMBER", expression.this, fmt)
3855 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3856 this = self.sql(expression, "this") 3857 kind = self.sql(expression, "kind") 3858 settings_sql = self.expressions(expression, key="settings", sep=" ") 3859 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3860 return f"{this}({kind}{args})"
3879 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3880 expressions = self.expressions(expression, flat=True) 3881 expressions = f" {self.wrap(expressions)}" if expressions else "" 3882 buckets = self.sql(expression, "buckets") 3883 kind = self.sql(expression, "kind") 3884 buckets = f" BUCKETS {buckets}" if buckets else "" 3885 order = self.sql(expression, "order") 3886 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3891 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3892 expressions = self.expressions(expression, key="expressions", flat=True) 3893 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3894 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3895 buckets = self.sql(expression, "buckets") 3896 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3898 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3899 this = self.sql(expression, "this") 3900 having = self.sql(expression, "having") 3901 3902 if having: 3903 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3904 3905 return self.func("ANY_VALUE", this)
3907 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3908 transform = self.func("TRANSFORM", *expression.expressions) 3909 row_format_before = self.sql(expression, "row_format_before") 3910 row_format_before = f" {row_format_before}" if row_format_before else "" 3911 record_writer = self.sql(expression, "record_writer") 3912 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3913 using = f" USING {self.sql(expression, 'command_script')}" 3914 schema = self.sql(expression, "schema") 3915 schema = f" AS {schema}" if schema else "" 3916 row_format_after = self.sql(expression, "row_format_after") 3917 row_format_after = f" {row_format_after}" if row_format_after else "" 3918 record_reader = self.sql(expression, "record_reader") 3919 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3920 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
3922 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3923 key_block_size = self.sql(expression, "key_block_size") 3924 if key_block_size: 3925 return f"KEY_BLOCK_SIZE = {key_block_size}" 3926 3927 using = self.sql(expression, "using") 3928 if using: 3929 return f"USING {using}" 3930 3931 parser = self.sql(expression, "parser") 3932 if parser: 3933 return f"WITH PARSER {parser}" 3934 3935 comment = self.sql(expression, "comment") 3936 if comment: 3937 return f"COMMENT {comment}" 3938 3939 visible = expression.args.get("visible") 3940 if visible is not None: 3941 return "VISIBLE" if visible else "INVISIBLE" 3942 3943 engine_attr = self.sql(expression, "engine_attr") 3944 if engine_attr: 3945 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3946 3947 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3948 if secondary_engine_attr: 3949 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3950 3951 self.unsupported("Unsupported index constraint option.") 3952 return ""
3958 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3959 kind = self.sql(expression, "kind") 3960 kind = f"{kind} INDEX" if kind else "INDEX" 3961 this = self.sql(expression, "this") 3962 this = f" {this}" if this else "" 3963 index_type = self.sql(expression, "index_type") 3964 index_type = f" USING {index_type}" if index_type else "" 3965 expressions = self.expressions(expression, flat=True) 3966 expressions = f" ({expressions})" if expressions else "" 3967 options = self.expressions(expression, key="options", sep=" ") 3968 options = f" {options}" if options else "" 3969 return f"{kind}{this}{index_type}{expressions}{options}"
3971 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3972 if self.NVL2_SUPPORTED: 3973 return self.function_fallback_sql(expression) 3974 3975 case = exp.Case().when( 3976 expression.this.is_(exp.null()).not_(copy=False), 3977 expression.args["true"], 3978 copy=False, 3979 ) 3980 else_cond = expression.args.get("false") 3981 if else_cond: 3982 case.else_(else_cond, copy=False) 3983 3984 return self.sql(case)
3986 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3987 this = self.sql(expression, "this") 3988 expr = self.sql(expression, "expression") 3989 iterator = self.sql(expression, "iterator") 3990 condition = self.sql(expression, "condition") 3991 condition = f" IF {condition}" if condition else "" 3992 return f"{this} FOR {expr} IN {iterator}{condition}"
4000 def predict_sql(self, expression: exp.Predict) -> str: 4001 model = self.sql(expression, "this") 4002 model = f"MODEL {model}" 4003 table = self.sql(expression, "expression") 4004 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4005 parameters = self.sql(expression, "params_struct") 4006 return self.func("PREDICT", model, table, parameters or None)
4018 def toarray_sql(self, expression: exp.ToArray) -> str: 4019 arg = expression.this 4020 if not arg.type: 4021 from sqlglot.optimizer.annotate_types import annotate_types 4022 4023 arg = annotate_types(arg, dialect=self.dialect) 4024 4025 if arg.is_type(exp.DataType.Type.ARRAY): 4026 return self.sql(arg) 4027 4028 cond_for_null = arg.is_(exp.null()) 4029 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4031 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4032 this = expression.this 4033 time_format = self.format_time(expression) 4034 4035 if time_format: 4036 return self.sql( 4037 exp.cast( 4038 exp.StrToTime(this=this, format=expression.args["format"]), 4039 exp.DataType.Type.TIME, 4040 ) 4041 ) 4042 4043 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4044 return self.sql(this) 4045 4046 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4048 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4049 this = expression.this 4050 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4051 return self.sql(this) 4052 4053 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4055 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4056 this = expression.this 4057 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4058 return self.sql(this) 4059 4060 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4062 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4063 this = expression.this 4064 time_format = self.format_time(expression) 4065 4066 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4067 return self.sql( 4068 exp.cast( 4069 exp.StrToTime(this=this, format=expression.args["format"]), 4070 exp.DataType.Type.DATE, 4071 ) 4072 ) 4073 4074 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4075 return self.sql(this) 4076 4077 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4089 def lastday_sql(self, expression: exp.LastDay) -> str: 4090 if self.LAST_DAY_SUPPORTS_DATE_PART: 4091 return self.function_fallback_sql(expression) 4092 4093 unit = expression.text("unit") 4094 if unit and unit != "MONTH": 4095 self.unsupported("Date parts are not supported in LAST_DAY.") 4096 4097 return self.func("LAST_DAY", expression.this)
4106 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4107 if self.CAN_IMPLEMENT_ARRAY_ANY: 4108 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4109 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4110 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4111 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4112 4113 from sqlglot.dialects import Dialect 4114 4115 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4116 if self.dialect.__class__ != Dialect: 4117 self.unsupported("ARRAY_ANY is unsupported") 4118 4119 return self.function_fallback_sql(expression)
4121 def struct_sql(self, expression: exp.Struct) -> str: 4122 expression.set( 4123 "expressions", 4124 [ 4125 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4126 if isinstance(e, exp.PropertyEQ) 4127 else e 4128 for e in expression.expressions 4129 ], 4130 ) 4131 4132 return self.function_fallback_sql(expression)
4140 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4141 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4142 tables = f" {self.expressions(expression)}" 4143 4144 exists = " IF EXISTS" if expression.args.get("exists") else "" 4145 4146 on_cluster = self.sql(expression, "cluster") 4147 on_cluster = f" {on_cluster}" if on_cluster else "" 4148 4149 identity = self.sql(expression, "identity") 4150 identity = f" {identity} IDENTITY" if identity else "" 4151 4152 option = self.sql(expression, "option") 4153 option = f" {option}" if option else "" 4154 4155 partition = self.sql(expression, "partition") 4156 partition = f" {partition}" if partition else "" 4157 4158 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4162 def convert_sql(self, expression: exp.Convert) -> str: 4163 to = expression.this 4164 value = expression.expression 4165 style = expression.args.get("style") 4166 safe = expression.args.get("safe") 4167 strict = expression.args.get("strict") 4168 4169 if not to or not value: 4170 return "" 4171 4172 # Retrieve length of datatype and override to default if not specified 4173 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4174 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4175 4176 transformed: t.Optional[exp.Expression] = None 4177 cast = exp.Cast if strict else exp.TryCast 4178 4179 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4180 if isinstance(style, exp.Literal) and style.is_int: 4181 from sqlglot.dialects.tsql import TSQL 4182 4183 style_value = style.name 4184 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4185 if not converted_style: 4186 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4187 4188 fmt = exp.Literal.string(converted_style) 4189 4190 if to.this == exp.DataType.Type.DATE: 4191 transformed = exp.StrToDate(this=value, format=fmt) 4192 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4193 transformed = exp.StrToTime(this=value, format=fmt) 4194 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4195 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4196 elif to.this == exp.DataType.Type.TEXT: 4197 transformed = exp.TimeToStr(this=value, format=fmt) 4198 4199 if not transformed: 4200 transformed = cast(this=value, to=to, safe=safe) 4201 4202 return self.sql(transformed)
4262 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4263 option = self.sql(expression, "this") 4264 4265 if expression.expressions: 4266 upper = option.upper() 4267 4268 # Snowflake FILE_FORMAT options are separated by whitespace 4269 sep = " " if upper == "FILE_FORMAT" else ", " 4270 4271 # Databricks copy/format options do not set their list of values with EQ 4272 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4273 values = self.expressions(expression, flat=True, sep=sep) 4274 return f"{option}{op}({values})" 4275 4276 value = self.sql(expression, "expression") 4277 4278 if not value: 4279 return option 4280 4281 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4282 4283 return f"{option}{op}{value}"
4285 def credentials_sql(self, expression: exp.Credentials) -> str: 4286 cred_expr = expression.args.get("credentials") 4287 if isinstance(cred_expr, exp.Literal): 4288 # Redshift case: CREDENTIALS <string> 4289 credentials = self.sql(expression, "credentials") 4290 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4291 else: 4292 # Snowflake case: CREDENTIALS = (...) 4293 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4294 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4295 4296 storage = self.sql(expression, "storage") 4297 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4298 4299 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4300 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4301 4302 iam_role = self.sql(expression, "iam_role") 4303 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4304 4305 region = self.sql(expression, "region") 4306 region = f" REGION {region}" if region else "" 4307 4308 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4310 def copy_sql(self, expression: exp.Copy) -> str: 4311 this = self.sql(expression, "this") 4312 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4313 4314 credentials = self.sql(expression, "credentials") 4315 credentials = self.seg(credentials) if credentials else "" 4316 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4317 files = self.expressions(expression, key="files", flat=True) 4318 4319 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4320 params = self.expressions( 4321 expression, 4322 key="params", 4323 sep=sep, 4324 new_line=True, 4325 skip_last=True, 4326 skip_first=True, 4327 indent=self.COPY_PARAMS_ARE_WRAPPED, 4328 ) 4329 4330 if params: 4331 if self.COPY_PARAMS_ARE_WRAPPED: 4332 params = f" WITH ({params})" 4333 elif not self.pretty: 4334 params = f" {params}" 4335 4336 return f"COPY{this}{kind} {files}{credentials}{params}"
4341 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4342 on_sql = "ON" if expression.args.get("on") else "OFF" 4343 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4344 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4345 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4346 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4347 4348 if filter_col or retention_period: 4349 on_sql = self.func("ON", filter_col, retention_period) 4350 4351 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4353 def maskingpolicycolumnconstraint_sql( 4354 self, expression: exp.MaskingPolicyColumnConstraint 4355 ) -> str: 4356 this = self.sql(expression, "this") 4357 expressions = self.expressions(expression, flat=True) 4358 expressions = f" USING ({expressions})" if expressions else "" 4359 return f"MASKING POLICY {this}{expressions}"
4369 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4370 this = self.sql(expression, "this") 4371 expr = expression.expression 4372 4373 if isinstance(expr, exp.Func): 4374 # T-SQL's CLR functions are case sensitive 4375 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4376 else: 4377 expr = self.sql(expression, "expression") 4378 4379 return self.scope_resolution(expr, this)
4387 def rand_sql(self, expression: exp.Rand) -> str: 4388 lower = self.sql(expression, "lower") 4389 upper = self.sql(expression, "upper") 4390 4391 if lower and upper: 4392 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4393 return self.func("RAND", expression.this)
4395 def changes_sql(self, expression: exp.Changes) -> str: 4396 information = self.sql(expression, "information") 4397 information = f"INFORMATION => {information}" 4398 at_before = self.sql(expression, "at_before") 4399 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4400 end = self.sql(expression, "end") 4401 end = f"{self.seg('')}{end}" if end else "" 4402 4403 return f"CHANGES ({information}){at_before}{end}"
4405 def pad_sql(self, expression: exp.Pad) -> str: 4406 prefix = "L" if expression.args.get("is_left") else "R" 4407 4408 fill_pattern = self.sql(expression, "fill_pattern") or None 4409 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4410 fill_pattern = "' '" 4411 4412 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4418 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4419 generate_series = exp.GenerateSeries(**expression.args) 4420 4421 parent = expression.parent 4422 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4423 parent = parent.parent 4424 4425 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4426 return self.sql(exp.Unnest(expressions=[generate_series])) 4427 4428 if isinstance(parent, exp.Select): 4429 self.unsupported("GenerateSeries projection unnesting is not supported.") 4430 4431 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4433 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4434 exprs = expression.expressions 4435 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4436 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4437 else: 4438 rhs = self.expressions(expression) 4439 4440 return self.func(name, expression.this, rhs or None)
4442 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4443 if self.SUPPORTS_CONVERT_TIMEZONE: 4444 return self.function_fallback_sql(expression) 4445 4446 source_tz = expression.args.get("source_tz") 4447 target_tz = expression.args.get("target_tz") 4448 timestamp = expression.args.get("timestamp") 4449 4450 if source_tz and timestamp: 4451 timestamp = exp.AtTimeZone( 4452 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4453 ) 4454 4455 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4456 4457 return self.sql(expr)
4459 def json_sql(self, expression: exp.JSON) -> str: 4460 this = self.sql(expression, "this") 4461 this = f" {this}" if this else "" 4462 4463 _with = expression.args.get("with") 4464 4465 if _with is None: 4466 with_sql = "" 4467 elif not _with: 4468 with_sql = " WITHOUT" 4469 else: 4470 with_sql = " WITH" 4471 4472 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4473 4474 return f"JSON{this}{with_sql}{unique_sql}"
4476 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4477 def _generate_on_options(arg: t.Any) -> str: 4478 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4479 4480 path = self.sql(expression, "path") 4481 returning = self.sql(expression, "returning") 4482 returning = f" RETURNING {returning}" if returning else "" 4483 4484 on_condition = self.sql(expression, "on_condition") 4485 on_condition = f" {on_condition}" if on_condition else "" 4486 4487 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4489 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4490 else_ = "ELSE " if expression.args.get("else_") else "" 4491 condition = self.sql(expression, "expression") 4492 condition = f"WHEN {condition} THEN " if condition else else_ 4493 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4494 return f"{condition}{insert}"
4502 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4503 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4504 empty = expression.args.get("empty") 4505 empty = ( 4506 f"DEFAULT {empty} ON EMPTY" 4507 if isinstance(empty, exp.Expression) 4508 else self.sql(expression, "empty") 4509 ) 4510 4511 error = expression.args.get("error") 4512 error = ( 4513 f"DEFAULT {error} ON ERROR" 4514 if isinstance(error, exp.Expression) 4515 else self.sql(expression, "error") 4516 ) 4517 4518 if error and empty: 4519 error = ( 4520 f"{empty} {error}" 4521 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4522 else f"{error} {empty}" 4523 ) 4524 empty = "" 4525 4526 null = self.sql(expression, "null") 4527 4528 return f"{empty}{error}{null}"
4534 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4535 this = self.sql(expression, "this") 4536 path = self.sql(expression, "path") 4537 4538 passing = self.expressions(expression, "passing") 4539 passing = f" PASSING {passing}" if passing else "" 4540 4541 on_condition = self.sql(expression, "on_condition") 4542 on_condition = f" {on_condition}" if on_condition else "" 4543 4544 path = f"{path}{passing}{on_condition}" 4545 4546 return self.func("JSON_EXISTS", this, path)
4548 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4549 array_agg = self.function_fallback_sql(expression) 4550 4551 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4552 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4553 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4554 parent = expression.parent 4555 if isinstance(parent, exp.Filter): 4556 parent_cond = parent.expression.this 4557 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4558 else: 4559 this = expression.this 4560 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4561 if this.find(exp.Column): 4562 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4563 this_sql = ( 4564 self.expressions(this) 4565 if isinstance(this, exp.Distinct) 4566 else self.sql(expression, "this") 4567 ) 4568 4569 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4570 4571 return array_agg
4579 def grant_sql(self, expression: exp.Grant) -> str: 4580 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4581 4582 kind = self.sql(expression, "kind") 4583 kind = f" {kind}" if kind else "" 4584 4585 securable = self.sql(expression, "securable") 4586 securable = f" {securable}" if securable else "" 4587 4588 principals = self.expressions(expression, key="principals", flat=True) 4589 4590 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4591 4592 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4616 def overlay_sql(self, expression: exp.Overlay): 4617 this = self.sql(expression, "this") 4618 expr = self.sql(expression, "expression") 4619 from_sql = self.sql(expression, "from") 4620 for_sql = self.sql(expression, "for") 4621 for_sql = f" FOR {for_sql}" if for_sql else "" 4622 4623 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4629 def string_sql(self, expression: exp.String) -> str: 4630 this = expression.this 4631 zone = expression.args.get("zone") 4632 4633 if zone: 4634 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4635 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4636 # set for source_tz to transpile the time conversion before the STRING cast 4637 this = exp.ConvertTimezone( 4638 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4639 ) 4640 4641 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4651 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4652 filler = self.sql(expression, "this") 4653 filler = f" {filler}" if filler else "" 4654 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4655 return f"TRUNCATE{filler} {with_count}"
4657 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4658 if self.SUPPORTS_UNIX_SECONDS: 4659 return self.function_fallback_sql(expression) 4660 4661 start_ts = exp.cast( 4662 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4663 ) 4664 4665 return self.sql( 4666 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4667 )
4669 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4670 dim = expression.expression 4671 4672 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4673 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4674 if not (dim.is_int and dim.name == "1"): 4675 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4676 dim = None 4677 4678 # If dimension is required but not specified, default initialize it 4679 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4680 dim = exp.Literal.number(1) 4681 4682 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4684 def attach_sql(self, expression: exp.Attach) -> str: 4685 this = self.sql(expression, "this") 4686 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4687 expressions = self.expressions(expression) 4688 expressions = f" ({expressions})" if expressions else "" 4689 4690 return f"ATTACH{exists_sql} {this}{expressions}"
4704 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4705 this_sql = self.sql(expression, "this") 4706 if isinstance(expression.this, exp.Table): 4707 this_sql = f"TABLE {this_sql}" 4708 4709 return self.func( 4710 "FEATURES_AT_TIME", 4711 this_sql, 4712 expression.args.get("time"), 4713 expression.args.get("num_rows"), 4714 expression.args.get("ignore_feature_nulls"), 4715 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4722 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4723 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4724 encode = f"{encode} {self.sql(expression, 'this')}" 4725 4726 properties = expression.args.get("properties") 4727 if properties: 4728 encode = f"{encode} {self.properties(properties)}" 4729 4730 return encode
4732 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4733 this = self.sql(expression, "this") 4734 include = f"INCLUDE {this}" 4735 4736 column_def = self.sql(expression, "column_def") 4737 if column_def: 4738 include = f"{include} {column_def}" 4739 4740 alias = self.sql(expression, "alias") 4741 if alias: 4742 include = f"{include} AS {alias}" 4743 4744 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4750 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4751 partitions = self.expressions(expression, "partition_expressions") 4752 create = self.expressions(expression, "create_expressions") 4753 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4755 def partitionbyrangepropertydynamic_sql( 4756 self, expression: exp.PartitionByRangePropertyDynamic 4757 ) -> str: 4758 start = self.sql(expression, "start") 4759 end = self.sql(expression, "end") 4760 4761 every = expression.args["every"] 4762 if isinstance(every, exp.Interval) and every.this.is_string: 4763 every.this.replace(exp.Literal.number(every.name)) 4764 4765 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4778 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4779 kind = self.sql(expression, "kind") 4780 option = self.sql(expression, "option") 4781 option = f" {option}" if option else "" 4782 this = self.sql(expression, "this") 4783 this = f" {this}" if this else "" 4784 columns = self.expressions(expression) 4785 columns = f" {columns}" if columns else "" 4786 return f"{kind}{option} STATISTICS{this}{columns}"
4788 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4789 this = self.sql(expression, "this") 4790 columns = self.expressions(expression) 4791 inner_expression = self.sql(expression, "expression") 4792 inner_expression = f" {inner_expression}" if inner_expression else "" 4793 update_options = self.sql(expression, "update_options") 4794 update_options = f" {update_options} UPDATE" if update_options else "" 4795 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4806 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4807 kind = self.sql(expression, "kind") 4808 this = self.sql(expression, "this") 4809 this = f" {this}" if this else "" 4810 inner_expression = self.sql(expression, "expression") 4811 return f"VALIDATE {kind}{this}{inner_expression}"
4813 def analyze_sql(self, expression: exp.Analyze) -> str: 4814 options = self.expressions(expression, key="options", sep=" ") 4815 options = f" {options}" if options else "" 4816 kind = self.sql(expression, "kind") 4817 kind = f" {kind}" if kind else "" 4818 this = self.sql(expression, "this") 4819 this = f" {this}" if this else "" 4820 mode = self.sql(expression, "mode") 4821 mode = f" {mode}" if mode else "" 4822 properties = self.sql(expression, "properties") 4823 properties = f" {properties}" if properties else "" 4824 partition = self.sql(expression, "partition") 4825 partition = f" {partition}" if partition else "" 4826 inner_expression = self.sql(expression, "expression") 4827 inner_expression = f" {inner_expression}" if inner_expression else "" 4828 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4830 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4831 this = self.sql(expression, "this") 4832 namespaces = self.expressions(expression, key="namespaces") 4833 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4834 passing = self.expressions(expression, key="passing") 4835 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4836 columns = self.expressions(expression, key="columns") 4837 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4838 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4839 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4845 def export_sql(self, expression: exp.Export) -> str: 4846 this = self.sql(expression, "this") 4847 connection = self.sql(expression, "connection") 4848 connection = f"WITH CONNECTION {connection} " if connection else "" 4849 options = self.sql(expression, "options") 4850 return f"EXPORT DATA {connection}{options} AS {this}"
4855 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4856 variable = self.sql(expression, "this") 4857 default = self.sql(expression, "default") 4858 default = f" = {default}" if default else "" 4859 4860 kind = self.sql(expression, "kind") 4861 if isinstance(expression.args.get("kind"), exp.Schema): 4862 kind = f"TABLE {kind}" 4863 4864 return f"{variable} AS {kind}{default}"
4866 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4867 kind = self.sql(expression, "kind") 4868 this = self.sql(expression, "this") 4869 set = self.sql(expression, "expression") 4870 using = self.sql(expression, "using") 4871 using = f" USING {using}" if using else "" 4872 4873 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4874 4875 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
4894 def put_sql(self, expression: exp.Put) -> str: 4895 props = expression.args.get("properties") 4896 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4897 this = self.sql(expression, "this") 4898 target = self.sql(expression, "target") 4899 return f"PUT {this} {target}{props_sql}"